data <- read.csv("https://raw.githubusercontent.com/sameralzaim/Project-3/main/bodyPerformance.csv", 
                   header = TRUE, sep = ",", stringsAsFactors = FALSE)

#head (data, n=10)

1 Introducion

This data was collected by the Korean Sports Promotion Foundation. It consists of measurement data for each physical fitness measurement item. The data provides itemized measurement information of the National Physical Fitness Measurement Data managed by the Seoul Olympic Commemorative National Sports Promotion Corporation.

The Analysis is 2 folded: + Predicting number of Sit-ips an athlete can perform based on other body and performance measurs. + Drew and inference between body and performance measurement and the probability of the athlete being in top performing class (“A”). for that we will group the “Class” variable into 2 classes, “A” and “Non A” that combines “B”, “C” and “D”

1.1 Itemized Variables List

The data consist of 13393 observations and 12 variables.

Variable Description Class
age Age between 20–64 num
gender F, M chr
height_cm Height in centimeters num
weight_kg Weight in kilograms num
body.fat_. Body fat index num
diastolic Blood pressure (diastolic) num
systolic Blood pressure (systolic) num
gripForce Measured in kg num
sit.and.bend.forward_cm Number of sit & bends per minute num
sit.ups.counts Number of sit-ups in 2 minutes num
broad.jump_cm Number of broad jumps num
class Fitness level (A: best, D: lowest) chr

1.2 Checking For Missing data

No missing data in the database

[1] 0

1.3 Data Distribution & Outliers

overall high level look at the the data distribution, does not show any concerns with the data. while we will be looking at each variables in details late on, looking at the below tables, shows that males participant are almost double of the females participant. Also, we can see majority are in the age of 20-30 which expected since this athletic performance data. we can also see that sit-ups, board jumps and grip force are almost normally distributed while sit and bend concentrated between 40-50.

layout(matrix(1:12, nrow = 3), widths = c(1, 1, 1, 1))
# Create a frequency table Gender
gender_counts <- table(data$gender)

# Make the barplot and capture the midpoints of bars
bp <- barplot(gender_counts,
              main = "Distributio by Gender",
              col = "skyblue",
              xlab = "Gender",
              ylim = c(0, max(gender_counts) * 1.4))  # Add a little space for the labels

# Add the text labels (counts)
text(x = bp, y = gender_counts, labels = gender_counts, pos = 3, cex = 0.9)


# Create a frequency table
class_counts <- table(data$class)

# Make the barplot and capture the midpoints of bars
bp <- barplot(class_counts,
              main = "Distribution by Class",
              col = "skyblue",
              xlab = "Class",
              ylim = c(0, max(class_counts) * 1.4))  # Add a little space for the labels

# Add the text labels (counts)
text(x = bp, y = class_counts, labels = class_counts, pos = 3, cex = 0.9)

hist(data$body.fat, main="Bdy Fat", col="skyblue", xlab="Value", breaks=6)
hist(data$weight_kg, main="Weight KG", col="skyblue", xlab="Value", breaks=6)
hist(data$height_cm, main="Height CM", col="skyblue", xlab="Value", breaks=6)
hist(data$diastolic, main="Diastolic", col="skyblue", xlab="Value", breaks=6)
hist(data$systolic, main="Sysytolic", col="skyblue", xlab="Value", breaks=6)

hist(data$gripForce, main="gripForce", col="skyblue", xlab="Value", breaks=6)
hist(data$sit.and.bend.forward_cm, main="sit.and.bend.forward_cm", col="skyblue", xlab="Value", breaks=6)
hist(data$sit.ups.counts, main="sit.ups.counts", col="skyblue", xlab="Value", breaks=6)
hist(data$broad.jump_cm, main="broad.jump_cm", col="skyblue", xlab="Value", breaks=6)
hist(data$age, main="age", col="skyblue", xlab="Value", breaks=6)

🟦 Gender: Data more skewed toward higher males participants with males / females distribution at 2/3 to 1/3.

🟦 Class: Data equally distributed across the 4 classes.

🟦 Age: Data skewed toward younger participant with, almost 45% in the age group 20-30.

# Set up layout: matrix of one row, two columns with widths 2:1
layout(matrix(1:3, nrow = 1), widths = c(1, 1, 1))

# Create a frequency table Gender
gender_counts <- table(data$gender)

# Make the barplot and capture the midpoints of bars
bp <- barplot(gender_counts,
              main = "Distributio by Gender",
              col = "skyblue",
              xlab = "Gender",
              ylim = c(0, max(gender_counts) * 1.1))  # Add a little space for the labels

# Add the text labels (counts)
text(x = bp, y = gender_counts, labels = gender_counts, pos = 3, cex = 0.9)


# Create a frequency table
class_counts <- table(data$class)

# Make the barplot and capture the midpoints of bars
bp <- barplot(class_counts,
              main = "Distribution by Class",
              col = "skyblue",
              xlab = "Class",
              ylim = c(0, max(class_counts) * 1.1))  # Add a little space for the labels

# Add the text labels (counts)
text(x = bp, y = class_counts, labels = class_counts, pos = 3, cex = 0.9)

# Create a frequency table
age_counts <- table(data$age)

hist(data$age, main="Distribution by Age", col="skyblue", xlab="Age", breaks=5)

🟥 Body Fat: Typical range: ~15% to 35% with presence of outliers above 40% and some even above 75%. These outliers could reflect individuals with obesity or possibly data entry errors—worth checking.

🟦 Weight (kg): Typical range: ~55 kg to 85 kg. the data have outliers on both ends with lots of individuals above 100 kg — expected in populations with higher BMI and a few below 40 kg — might be very lean individuals or young participants

Consider stratifying by gender or age if these values seem inconsistent

🟪 Height (CM): Typical range: ~160 to 180 cm. A few values below 140 cm — This could be females or outliers (possibly even data error) and a couple near 195 cm.

🟩 Diastolic (mm Hg): Typical range: ~65 to 90 mm Hg. outliers with values below 30 and above 120 — potentially serious medical conditions or data entry errors. Diastolic values below 30 are quite rare physiologically

🟥 Systolic (mm Hg): Typical range: ~100 to 145 mm Hg with several very low values (< 50 mm Hg) and very high values (> 180 mm Hg). These may indicate hypotensive or hypertensive patients, or could be errors in data capture

layout(matrix(1:5, nrow = 1), widths = c(1, 1, 1, 1, 1))

boxplot(data$body.fat_., main="Body Fat", col="tomato")
boxplot(data$weight_kg, main="Weight (kg)", col="skyblue")
boxplot(data$height_cm, main="Height (CM)", col="purple1")
boxplot(data$diastolic, main="Diastolic", col="cyan4")
boxplot(data$systolic, main="systolic", col="brown2")

🟥 Grip Force : Median around 38–40 with the distribution being moderately spread (not skewed). A few outliers below (extremely low grip forces, possibly errors or true weak measurements).

🟪 Sit & Bend: Very narrow distribution, but some extreme outliers way above 200 and a few low outliers, possibly due to stiff individuals or misreporting. Possibly a skewed distribution.

🟦 Sit-ups Count: Median around 40. Fairly symmetric. A few low outliers (someone maybe stopped early or had an injury?).

🟩 Broad Jump (CM): Median near 200 with wide distribution..Quite a few outliers below 100, indicating either low performance or measurement issues.

🟧 Age: Median around 30 and distribution skewed slightly right. No putliers observed.

layout(matrix(1:5, nrow = 1), widths = c(1, 1, 1, 1, 1))

boxplot(data$gripForce, main="Grip Force", col="brown2")
boxplot(data$sit.and.bend.forward_cm, main="Sit & Bend", col="purple1")
boxplot(data$sit.ups.counts, main="Sit-ups Count", col="skyblue")
boxplot(data$broad.jump_cm, main="Broad Jump (CM)", col="cyan4")
boxplot(data$age, main="age", col="tomato")

1.4 Evaluating Weight by Gender

🟧 Females (F): As expected, median weight is lower than for males with Majority lie roughly between ~50 kg and 70 kg.

There’s a wider spread of outliers on the high end (some women over 100 kg and a few low-weight outliers below 45 kg.

🟦 Males (M): Median weight around 70–75 kg with the middle 50% of male weights span ~65 kg to 90 kg. Males have more outliers overall — both low and high ends.

Overall, weight is right-skewed for both groups — many outliers appear on the high end of the weight scale, suggesting some heavier individuals pull the mean up. We would need to check the outliers for potential data entry errors or valid extreme cases.

plot_ly(data, 
        x = ~weight_kg, 
        y = ~gender, 
        type = "box", 
        color = ~gender,
                colors = c("cyan4", "red"),
         mode = 'lines+markers', 
        line = list(color = 'black'),
        marker = list(color = 'black', size = 4))
          # Customize colors here

2 Predicting Number of Sit ups

To achieve that, need to build Regression Tree. We use the data we have to train an algorithm for predicting number of Sit-up an athlete can perform.

This process requires multiple steps as follows:

2.1 Tree Induction:

in this step we build the main tree utilizing the training dataset where we evaluate all possible featurs for spliting after deciding stopping rules that would tell our algorithm when to stop. In this process here, we used default rules stopping rules without tree pruning as follows: + Min observations in any node to perform a split is 20 + Min observations in terminal node 7 + Max depth of the tree 5 levels.

The above stopping rules we arrived at after attempting multiple combinations and values.

# Set seed for reproducibility
set.seed(123)


# Split data into training (70%) and test (30%) sets
train.index <- sample(1:nrow(data), size = 0.7 * nrow(data))
train.data <- data[train.index, ]
test.data <- data[-train.index, ]

# 1. Tree Induction & 2. Splitting Criteria
# Build the initial regression tree using rpart
tree.model <- rpart(sit.ups.counts ~ ., 
                    data = train.data,
                    method = "anova",     # For regression
                    control = rpart.control(
                      minsplit = 20,    # 3. Stopping rule: min observations to split
                      minbucket = 10,    # Min observations in terminal node
                      cp = seq(0, 0.05, 20), # Complexity parameter
                      maxdepth = 6      # Maximum tree depth
                    ))

# Visualize the unpruned tree
rpart.plot(tree.model, main = "Initial Regression Tree")

The initial tree contains a long list of different pieces of information that can be used to improve the initial tree model. In order to improve the tree, wee look at the model complexity parameter cp and related errors and appropriately prune the initial tree.

2.2 Pruning Process

The below complexity table shows key information we need for our tree pruning based on cross-validation:

  • Complexity parameter values: Penalty term that balances tree complexity with fit quality
  • Number of splits in the Tree (nsplit)
  • Relative error (rel error) Calculated as: Error(current_tree)/Error(root_node)
  • Cross-validated error (xerror)
  • Standard error of the cross-validated error (xstd)
# Examine cross-validation results
pander(tree.model$cptable)
CP nsplit rel error xerror xstd
0.3806 0 1 1 0.01406
0.09712 1 0.6194 0.6196 0.008808
0.04086 2 0.5223 0.5249 0.008041
0.02818 3 0.4814 0.4841 0.007443
0.02312 4 0.4532 0.4556 0.007362
0.02114 5 0.4301 0.428 0.007038
0.01233 6 0.409 0.4105 0.006745
0.01093 7 0.3966 0.4015 0.006746
0.009386 8 0.3857 0.39 0.0066
0.00809 9 0.3763 0.3789 0.006345
0.008 10 0.3682 0.376 0.006277
0.004837 11 0.3602 0.3634 0.006194
0.004823 12 0.3554 0.36 0.006143
0.004627 13 0.3506 0.3572 0.006126
0.004579 14 0.3459 0.3556 0.006121
0.004333 15 0.3414 0.3526 0.006112
0.00428 16 0.337 0.3484 0.006062
0.004116 17 0.3327 0.344 0.00603
0.003791 18 0.3286 0.3383 0.005948
0.003518 19 0.3248 0.334 0.005891
0.003374 20 0.3213 0.3302 0.005706
0.002775 21 0.3179 0.3253 0.005537
0.002464 22 0.3152 0.3232 0.005549
0.002348 23 0.3127 0.3227 0.005548
0.002268 24 0.3104 0.3218 0.005534
0.002094 25 0.3081 0.3197 0.00547
0.00203 26 0.306 0.3175 0.005438
0.001464 27 0.304 0.3122 0.005348
0.001399 28 0.3025 0.3118 0.005359
0.00137 29 0.3011 0.312 0.005379
0.001365 30 0.2997 0.3119 0.005379
0.001276 31 0.2984 0.3117 0.005381
0.001141 32 0.2971 0.3098 0.005385
0.001099 33 0.296 0.3081 0.005377
0.00108 34 0.2949 0.3074 0.005372
0.001028 35 0.2938 0.3073 0.005376
0.0009767 36 0.2927 0.3064 0.005371
0.0008478 37 0.2918 0.3057 0.005346
0.000781 38 0.2909 0.3068 0.005418
0.0007593 39 0.2901 0.3066 0.005412
0.0007381 40 0.2894 0.3066 0.005411
0.0006773 41 0.2886 0.3067 0.005416
0.0006724 42 0.288 0.3071 0.005426
0.0006642 43 0.2873 0.3074 0.005427
0.0006458 44 0.2866 0.3076 0.005431
0.0006012 45 0.286 0.3074 0.005436
0.0005739 46 0.2854 0.3078 0.00549
0.0005654 47 0.2848 0.3081 0.005491
0.0005406 49 0.2837 0.3081 0.005494
0.0005262 50 0.2831 0.308 0.005486
0.0005177 51 0.2826 0.3083 0.005496
0.0005174 52 0.2821 0.3079 0.005489
0.000505 53 0.2816 0.3078 0.005487
0.000464 54 0.2811 0.3072 0.005483
0.0003884 55 0.2806 0.3067 0.005482
0.0003803 56 0.2802 0.3065 0.005481
0.0003274 57 0.2798 0.3064 0.005463
0.0003204 58 0.2795 0.3068 0.00547
0.0002966 59 0.2792 0.307 0.005471
0.0002495 60 0.2789 0.3072 0.005477
0 61 0.2786 0.3071 0.005497

from the above, we can see that min Cross validation error is 0.3178 with number of splits of 28 abd cp of 0.0007478.

2.3 Selecting Optimal Tree Size

Smallest nsplit where xerror is within 1 standard error (xstd) of the minimum CP. in the table represent 0.3196 with nsplit of 25. We need to identify the largest CP where xerror is within 1 standard error of the minimum (to balance simplicity and accuracy).

plotcp(tree.model)

As mentioned earlier, we select the largest cp where xerror is within 1 standard error of the minimum (to balance simplicity and accuracy). based on this our best possible tree would have 25 nodes and complexity

cp.table <- tree.model$cptable

## Identify the minimum `xerror` and its `cp`.
min.xerror <- min(cp.table[, "xerror"])
min.cp.row <- which.min(cp.table[, "xerror"])
min.cp <- cp.table[min.cp.row, "CP"]

## Get the standard error (`xstd`) of the minimum `xerror`
xerror.std <- cp.table[min.cp.row, "xstd"]
threshold <- min.xerror + xerror.std  # Upper bound (1 SE rule)

## Find the simplest tree (`cp`) Where `xerror less than or equal to Threshold`.
best.cp.row <- which(cp.table[, "xerror"] <= threshold)[1]  # First row meeting criteria
best.cp <- cp.table[best.cp.row, "CP"]

## Two different trees: best CP vs minimum CP
pruned.tree.best.cp <- prune(tree.model, cp = best.cp)
pruned.tree.min.cp <- prune(tree.model, cp = min.cp)

The above tree shows that our best cp = 0.0014 where the tree has 26 splits and cross validation error 0.3196

# Visualize the pruned tree: best CP
rpart.plot(pruned.tree.best.cp, main = paste("Pruned Tree (Best CP): cp = ", round(best.cp,4)))

The above min cp tree shows 30 splits but with higher complexity and lower increase in tree strength

# Visualize the pruned tree: minimum CP
rpart.plot(pruned.tree.min.cp, main = paste("Pruned Tree (Minimum CP): cp = ", round(min.cp,4)))

2.4 Building the linear regession model

Next, we use the final pruned regression tree to make predictions. Since only five features “abody.fat_.” + “gender” + “broad.jump_cm” + “class” + “age” were used in the algorithm.

As next step: we use the pruned regression tree with best and min cp to make predictions and since we did not use all variables, we fit fit two linear regression models and compare the performance of the three models.

The 2 linear regression models were built as follows:

  • LSE01: includes “body.fat_.” + “gender” + “broad.jump_cm” + “class” + “age”.
  • LSE02: including all variables through step-wise variable selection.
# 5. Prediction
# Make predictions on test data
pred.best.cp <- predict(pruned.tree.best.cp, newdata = test.data)
pred.min.cp <- predict(pruned.tree.min.cp, newdata = test.data)


# Evaluate model performance: best.cp
mse.tree.best.cp <- mean((test.data$sit.ups.counts - pred.best.cp)^2)
rmse.tree.best.cp <- sqrt(mse.tree.best.cp)
r.squared.tree.best.cp <- cor(test.data$sit.ups.counts, pred.best.cp)^2
# min.cp
mse.tree.min.cp <- mean((test.data$sit.ups.counts - pred.min.cp)^2)
rmse.tree.min.cp <- sqrt(mse.tree.min.cp)
r.squared.tree.min.cp <- cor(test.data$sit.ups.counts, pred.min.cp)^2

##
# fit ordinary least square regression 
LSE01 <- lm(sit.ups.counts ~ body.fat_. + gender + broad.jump_cm + class + age, data = train.data)
pred.lse01 <-  predict(LSE01, newdata = test.data)
mse.lse01 <- mean((test.data$sit.ups.counts - pred.lse01)^2)
rmse.lse01 <- sqrt(mse.lse01)
r.squared.lse01 <- cor(test.data$sit.ups.counts, pred.lse01)^2

##
## ordinary LSE regression model with step-wise variable selection
lse02.fit <- lm(sit.ups.counts~.,data = train.data)
AIC.fit <- stepAIC(lse02.fit, direction="both", trace = FALSE)
pred.lse02 <- predict(AIC.fit, test.data)
mse.lse02 <- mean((test.data$sit.ups.counts - pred.lse02)^2)    # mean square error
rmse.lse02 <- sqrt(mse.lse02)                       # root mean square error
r.squared.lse02 <- (cor(test.data$sit.ups.counts, pred.lse02))^2 # r-squared

###
Errors <- cbind(MSE = c(mse.tree.best.cp, mse.tree.min.cp, mse.lse01, mse.lse02),
                RMSE = c(rmse.tree.best.cp, rmse.tree.min.cp, rmse.lse01, rmse.lse02),
                r.squared = c(r.squared.tree.best.cp, r.squared.tree.min.cp, r.squared.lse01, r.squared.lse02))
rownames(Errors) = c("tree.best.cp", "tree.min.cp", "lse01", "lse02")
pander(Errors)
  MSE RMSE r.squared
tree.best.cp 61.85 7.864 0.7023
tree.min.cp 61.21 7.823 0.7054
lse01 54.52 7.384 0.7376
lse02 53.14 7.29 0.7442

unlike the expectation, it seems that the linear regression model out perform the tree where we compare “lse2” with “min cp”.

Model MSE ↓ Interpretation
lse02 53.14 0.744 Lower error + captures ~74% of variance
tree.best.cp 61.85 0.702 Simpler, but not as accurate

Hence, even with cp tuning, trees have a ceiling in how well they can model smooth, continuous data. Regression models, particularly when well-specified, often just do better for that kind of problem.

However, as we compare both outcome, we can well utilize the tree if needed, as it continue to provide strong separation with much less complexity.

2.5 Variable importance Best CP

Variable importance in regression trees identifies which predictors have the strongest influence on the target variable’s predictions

Comparing variables importance between minimum cp and best cp shos that both have the same dtributions as outlined in the below 2 graphs.

importance <- pruned.tree.best.cp$variable.importance
barplot(sort(importance, decreasing = TRUE), 
        main = "Variable Importance: Best CP",
        col = "skyblue",
        las = 2)

2.6 Variable importance Minimum CP

importance <- pruned.tree.min.cp$variable.importance
barplot(sort(importance, decreasing = TRUE), 
        main = "Variable Importance: Minimum CP",
        col = "skyblue",
                las = 2)

looking at the above tree we can see that some variables have higher importance but not showing in the tree. It is not uncommon that some variables in the variable importance list but not shown in the final regression and classification trees.

Importance is measured by performance gain (e.g., reduction in MSE/Gini impurity).This captures how much a variable improves model accuracy but does not imply statistical significance.

Larger coefficients indicate stronger associations with the outcome, though correlation does not imply causation and as such as height and grip force more linked to variable like gender where males have taller and stronger grip force but this confound with gender and since tree used gender then these are becoming redundent variables in tree build.

For comparison, we also print out the inferential table of the step-wise linear regression model in the following.

pander(summary(AIC.fit)$coef)
  Estimate Std. Error t value Pr(>|t|)
(Intercept) 57.88 3.041 19.03 2.893e-79
age -0.397 0.007227 -54.93 0
genderM 5.575 0.3692 15.1 6.609e-51
height_cm -0.1332 0.01883 -7.074 1.61e-12
weight_kg 0.1463 0.01469 9.96 2.962e-23
body.fat_. -0.2971 0.02023 -14.68 2.798e-48
diastolic 0.02365 0.007704 3.07 0.002146
gripForce 0.04457 0.01554 2.868 0.004141
sit.and.bend.forward_cm 0.01985 0.01243 1.598 0.1102
broad.jump_cm 0.07872 0.00407 19.34 9.629e-82
classB -4.209 0.2255 -18.66 2.422e-76
classC -7.467 0.2436 -30.66 8.64e-197
classD -13.68 0.31 -44.14 0

2.7 Conclusion

We would recommend using the tree though the performance of the linear regression model outperform the tree since we didn’t not remove variables that have strong correlation but not causation relationship with the treated variable such as “broad.jump_cm”

3 Estimating and Predicting Performance Class

To predict and calculate class variable, we use Classification trees. Classification trees are a type of supervised learning algorithm that recursively partitions the feature space to predict categorical target variables.

3.1 Grow Initial Tree

The initial tree size is controlled by some default hyper-parameters “rpart.control()”. It tends to be over-fitted.

While it is common in classification that the key challenge is that the classes (categories) are not equally represented.however, in our dataset the classes are equally distributed across the 4 classes. howevr. since we have 4 levels in “class” variable, we combine these to pridict the porbability of athelet being classified as “A” or “Not A”.

A we are building the tree, we start with the entire dataset at the root node and then recursively split the data into purer subsets

Optimal Tree Size: Typically where xerror is minimized

# Step 1: Recode the target variable into binary
data$binary_class <- ifelse(data$class == "A", 1, 0)
data$binary_class <- as.factor(data$binary_class)

# Step 2: Split data into training and test sets
set.seed(123)
train.index <- createDataPartition(data$binary_class, p = 0.7, list = FALSE)
train.data <- data[train.index, ]
test.data <- data[-train.index, ]

# Step 3: Fit the classification tree using the new binary target

tree.model <- rpart(binary_class ~ ., 
                    data = train.data[, !(names(train.data) %in% "class")],  # drop original class
                    method = "class",   # classification tree
                    parms = list(split = "gini",  # Using Gini index
                                 # FN cost = 1, FP cost = 0.5
                                 loss = matrix(c(0, 0.5, 1, 0), nrow = 2)  
                                 ),
                    control = rpart.control(minsplit = 20,  # Min 15 obs to split
                                           minbucket = 10,   # Min 7 obs in leaf
                                           # Complexity parameter
                                           cp = 0.001, # complex parameter
                                           maxdepth = 7))   # Max tree depth
rpart.plot(tree.model, 
           extra = 104, # check the help document for more information
           # color palette is a sequential color scheme that blends green (G) to blue (Bu)
           box.palette = "GnBu",  
           branch.lty = 1, 
           shadow.col = "gray", 
           nn = TRUE)

3.2 Pruning Tree

#Print the complexity parameter table
pander(tree.model$cptable)
CP nsplit rel error xerror xstd
0.02005 0 1 2 0.03578
0.01152 7 0.8332 1.384 0.03044
0.009954 8 0.8217 1.32 0.02978
0.007821 12 0.779 1.297 0.02959
0.004266 15 0.7555 1.27 0.02934
0.004124 16 0.7513 1.253 0.02918
0.002474 19 0.7389 1.245 0.02909
0.002453 29 0.7142 1.224 0.02881
0.001706 33 0.7044 1.202 0.0285
0.00128 34 0.7026 1.183 0.02827
0.001 35 0.7014 1.179 0.02815

The below graph gives the reference line (broken line) for 1-SE rule. The numbers on the top of the plot represent the leaf nodes in the final tree diagram.

# Print cp table
#printcp(tree.model)

# Plot cp vs cross-validated error
plotcp(tree.model)

For clarity in the analysis, we introduce two notations: min.cp represents the cp value yielding the minimum cross-validation error, while 1SE.cp denotes the cp value selected by the more conservative 1-SE rule (minimal error plus one standard error).

The below-pruned tree diagram is based on the 1-SE rule. Next, we plot the tree diagram based on the minimum cross-validation error.

# Find the optimal cp value that minimizes cross-validated error
min.cp <- tree.model$cptable[which.min(tree.model$cptable[,"xerror"]),"CP"]

# Prune the tree using the optimal cp
pruned.tree.1SE <- prune(tree.model, cp = 0.001)  
pruned.tree.min <- prune(tree.model, cp = min.cp)

# Visualize the pruned tree
rpart.plot(pruned.tree.1SE, 
           extra = 104, # check the help document for more information
           # color palette is a sequential color scheme that blends green (G) to blue (Bu)
           box.palette = "GnBu",  
           branch.lty = 1, 
           shadow.col = "gray", 
           nn = TRUE,
           main = "Pruned Classification Tree (1-SE Rule)")

The above-pruned tree diagram is based on the 1-SE rule. Next, we plot the tree diagram based on the minimum cross-validation error

# Visualize the pruned tree
rpart.plot(pruned.tree.min, 
           extra = 104, # check the help document for more information
           # color palette is a sequential color scheme that blends green (G) to blue (Bu)
           box.palette = "GnBu",  
           branch.lty = 1, 
           shadow.col = "gray", 
           nn = TRUE,
           main = "Pruned Classification Tree (Min Cross Validation)")

3.3 Global Performance with ROC

Classification trees make predictions by routing observations through a series of hierarchical splits, starting at the root node and ending at a terminal leaf node. Each split applies a decision rule based on feature values.

# Make predictions on the test set
pred.label.1SE <- predict(pruned.tree.1SE, test.data, type = "class") # default cutoff 0.5
pred.prob.1SE <- predict(pruned.tree.1SE, test.data, type = "prob")[,2]
##
pred.label.min <- predict(pruned.tree.min, test.data, type = "class") # default cutoff 0.5
pred.prob.min <- predict(pruned.tree.min, test.data, type = "prob")[,2]

# Confusion matrix
#conf.matrix <- confusionMatrix(pred.label, test.data$diabetes, positive = "pos")
#print(conf.matrix)

train.data$binary_class <- data$binary_class[train.index]
test.data$binary_class <- data$binary_class[-train.index]

# Remove class column if it still exists
train.data$class <- NULL
test.data$class <- NULL

########################
###  logistic regression
logit.fit <- glm(binary_class ~ ., data = train.data, family = binomial)
AIC.logit <- step(logit.fit, direction = "both", trace = 0)
pred.logit <- predict(AIC.logit, test.data, type = "response")

# ROC curve and AUC
roc.tree.1SE <- roc(test.data$binary_class, pred.prob.1SE)
roc.tree.min <- roc(test.data$binary_class, pred.prob.min)
roc.logit <- roc(test.data$binary_class, pred.logit)

##
### Sen-Spe
tree.1SE.sen <- roc.tree.1SE$sensitivities
tree.1SE.spe <- roc.tree.1SE$specificities
#
tree.min.sen <- roc.tree.min$sensitivities
tree.min.spe <- roc.tree.min$specificities

#
logit.sen <- roc.logit$sensitivities
logit.spe <- roc.logit$specificities
## AUC
auc.tree.1SE <- roc.tree.1SE$auc
auc.tree.min <- roc.tree.min$auc
auc.logit <- roc.logit$auc

###
plot(1-logit.spe, logit.sen,  
     xlab = "1 - specificity",
     ylab = "sensitivity",
     col = "darkred",
     type = "l",
     lty = 1,
     lwd = 1,
     main = "ROC: CART and Logistic Regressopm")
lines(1-tree.1SE.spe, tree.1SE.sen, 
      col = "blue",
      lty = 1,
      lwd = 1)
lines(1-tree.min.spe, tree.min.sen,      
      col = "orange",
      lty = 1,
      lwd = 1)
abline(0,1, col = "skyblue3", lty = 2, lwd = 2)
legend("bottomright", c("Logistic", "Tree 1SE", "Tree Min"),
       lty = c(1,1,1), lwd = rep(1,3),
       col = c("red", "blue", "orange"),
       bty="n",cex = 0.8)
## annotation - AUC
text(0.8, 0.46, paste("Logistic AUC: ", round(auc.logit,4)), cex = 0.8)
text(0.8, 0.4, paste("Tree 1SE AUC: ", round(auc.tree.1SE,4)), cex = 0.8)
text(0.8, 0.34, paste("Tree Min AUC: ", round(auc.tree.min,4)), cex = 0.8)

The ROC curves and corresponding AUC values demonstrate that the logistic regression model achieves marginally better performance compared to both pruned tree models, with the more complex tree (pruned using minimum cross-validation error) same predictive ability than the simpler tree pruned according to the 1-SE rule.

3.4 Optimal Cut-off Probability

In binary classification, predicted probabilities must be converted into class labels (e.g., 0 or 1) by applying a cut-off threshold. The choice of this threshold significantly impacts model performance, as it balances accuracy, sensitivity (recall), and specificity.

Key approaches to determine the optimal cut-off:

I. Trade-off Between Sensitivity and Specificity II. Accuracy-Driven Cut-off III. Cost-Sensitive Threshold IV. ROC and Precision-Recall Curves V. Practical Considerations

3.4.1 Cut-off Versus Misclassification Cost

# Predictive probabilities of the pruned tree
pred.prob.min <- predict(pruned.tree.min, train.data, type = "prob")[, 2]

# Cutoff values
cutoff <- seq(0, 1, length = 10)
cost <- numeric(length(cutoff))

# Misclassification cost for each cutoff
for (i in seq_along(cutoff)) {
  pred.label <- ifelse(pred.prob.min > cutoff[i], 1, 0)
  FN <- sum(pred.label == 0 & train.data$binary_class == 1)
  FP <- sum(pred.label == 1 & train.data$binary_class == 0)
  cost[i] <- 5 * FP + 20 * FN
}

# Optimal cutoff
min.ID <- which(cost == min(cost))
optim.prob <- mean(cutoff[min.ID])

# Plot
plot(cutoff, cost, type = "b", col = "navy",
     main = "Cutoff vs Misclassification Cost",
     xlab = "Cutoff", ylab = "Cost")
text(optim.prob, min(cost) + 20000, 
     paste("Optimal cutoff:", round(optim.prob, 3)), 
     cex = 0.8, col = "darkred")

The resulting optimal cut-off probability of 0.222, displayed on the plot above, will be used to make predictions on the test dataset, and the corresponding accuracy will be reported. We emphasize once again that this optimal threshold is chosen specifically to minimize the total cost of misclassification.

LS0tDQp0aXRsZTogIkJvZHkgUGVyZm9ybWFuY2UgQW5hbHlzaXMiDQphdXRob3I6ICJTYW1lciBBbHphaW0iDQpkYXRlOiAiICINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHdvcmRfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIGtlZXBfbWQ6IHllcw0KICBwZGZfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgZmlnX3dpZHRoOiAzDQogICAgZmlnX2hlaWdodDogMw0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KYGBgez1odG1sfQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQovKiBDYXNjYWRpbmcgU3R5bGUgU2hlZXRzIChDU1MpIGlzIGEgc3R5bGVzaGVldCBsYW5ndWFnZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBwcmVzZW50YXRpb24gb2YgYSBkb2N1bWVudCB3cml0dGVuIGluIEhUTUwgb3IgWE1MLiBpdCBpcyBhIHNpbXBsZSBtZWNoYW5pc20gZm9yIGFkZGluZyBzdHlsZSAoZS5nLiwgZm9udHMsIGNvbG9ycywgc3BhY2luZykgdG8gV2ViIGRvY3VtZW50cy4gKi8NCg0KaDEudGl0bGUgeyAgLyogVGl0bGUgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIHRoZSByZXBvcnQgdGl0bGUgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IG5hdnk7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBhdXRob3JzICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBuYXZ5Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgdGhlIGRhdGUgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDEgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDIycHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogY2VudGVyOw0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KaDIgeyAvKiBIZWFkZXIgMiAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDIgc2VjdGlvbiB0aXRsZSAqLw0KICAgIGZvbnQtc2l6ZTogMjBweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmg0IHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDQgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KYm9keSB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KLmhpZ2hsaWdodG1lIHsgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IH0NCg0KcCB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KPC9zdHlsZT4NCmBgYA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgY29kZSBjaHVuayBzcGVjaWZpZXMgd2hldGhlciB0aGUgUiBjb2RlLCB3YXJuaW5ncywgYW5kIG91dHB1dCANCiMgd2lsbCBiZSBpbmNsdWRlZCBpbiB0aGUgb3V0cHV0IGZpbGVzLg0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpDQogICBsaWJyYXJ5KGtuaXRyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KfQ0KaWYgKCFyZXF1aXJlKCJHR2FsbHkiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiR0dhbGx5IikNCmxpYnJhcnkoR0dhbGx5KQ0KfQ0KaWYgKCFyZXF1aXJlKCJnbG1uZXQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IikNCmxpYnJhcnkoZ2xtbmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJjYXJldCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpDQpsaWJyYXJ5KGNhcmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJNQVNTIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKQ0KbGlicmFyeShNQVNTKQ0KfQ0KaWYgKCFyZXF1aXJlKCJtbGJlbmNoIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIm1sYmVuY2giKQ0KbGlicmFyeShtbGJlbmNoKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwUk9DIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBST0MiKQ0KbGlicmFyeShwUk9DKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwbG90bHkiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikNCmxpYnJhcnkocGxvdGx5KQ0KfQ0KaWYgKCFyZXF1aXJlKCJwYW5kZXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGFuZGVyIikNCmxpYnJhcnkocGFuZGVyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJyYW5kb21Gb3Jlc3QiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicmFuZG9tRm9yZXN0IikNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIpDQpsaWJyYXJ5KHJwYXJ0KQ0KfQ0Kb3B0aW9ucyhyZXBvcyA9IGMoQ1JBTiA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmciKSkNCmluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KbGlicmFyeShycGFydC5wbG90KQ0KfQ0KDQojIyMgDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwgICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0cyA9IFRSVUUsICAgIA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gTkENCiAgICAgICAgICAgICAgICAgICAgICApICANCmBgYA0KDQoNCmBgYHtyfQ0KZGF0YSA8LSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NhbWVyYWx6YWltL1Byb2plY3QtMy9tYWluL2JvZHlQZXJmb3JtYW5jZS5jc3YiLCANCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiLCIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCg0KI2hlYWQgKGRhdGEsIG49MTApDQoNCmBgYA0KDQojIEludHJvZHVjaW9uDQoNClRoaXMgZGF0YSB3YXMgY29sbGVjdGVkIGJ5IHRoZSBLb3JlYW4gU3BvcnRzIFByb21vdGlvbiBGb3VuZGF0aW9uLiBJdCBjb25zaXN0cyBvZiBtZWFzdXJlbWVudCBkYXRhIGZvciBlYWNoIHBoeXNpY2FsIGZpdG5lc3MgbWVhc3VyZW1lbnQgaXRlbS4gVGhlIGRhdGEgcHJvdmlkZXMgaXRlbWl6ZWQgbWVhc3VyZW1lbnQgaW5mb3JtYXRpb24gb2YgdGhlIE5hdGlvbmFsIFBoeXNpY2FsIEZpdG5lc3MgTWVhc3VyZW1lbnQgRGF0YSBtYW5hZ2VkIGJ5IHRoZSBTZW91bCBPbHltcGljIENvbW1lbW9yYXRpdmUgTmF0aW9uYWwgU3BvcnRzIFByb21vdGlvbiBDb3Jwb3JhdGlvbi4NCg0KVGhlIEFuYWx5c2lzIGlzIDIgZm9sZGVkOg0KICArIFByZWRpY3RpbmcgbnVtYmVyIG9mIFNpdC1pcHMgYW4gYXRobGV0ZSBjYW4gcGVyZm9ybSBiYXNlZCBvbiBvdGhlciBib2R5IGFuZCBwZXJmb3JtYW5jZSBtZWFzdXJzLg0KICArIERyZXcgYW5kIGluZmVyZW5jZSBiZXR3ZWVuIGJvZHkgYW5kIHBlcmZvcm1hbmNlIG1lYXN1cmVtZW50IGFuZCB0aGUgcHJvYmFiaWxpdHkgb2YgdGhlIGF0aGxldGUgYmVpbmcgaW4gdG9wIHBlcmZvcm1pbmcgY2xhc3MgKCJBIikuIGZvciB0aGF0IHdlIHdpbGwgZ3JvdXAgdGhlICJDbGFzcyIgdmFyaWFibGUgaW50byAyIGNsYXNzZXMsICJBIiBhbmQgIk5vbiBBIiB0aGF0IGNvbWJpbmVzICJCIiwgIkMiIGFuZCAiRCINCg0KIyMgSXRlbWl6ZWQgVmFyaWFibGVzIExpc3QNCg0KVGhlIGRhdGEgY29uc2lzdCBvZiAxMzM5MyBvYnNlcnZhdGlvbnMgYW5kIDEyIHZhcmlhYmxlcy4NCg0KfCAqKlZhcmlhYmxlKiogICAgICAgICAgICAgIHwgKipEZXNjcmlwdGlvbioqICAgICAgICAgICAgICAgICAgICB8KipDbGFzcyoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS18DQp8IGFnZSAgICAgICAgICAgICAgICAgICAgICAgfCBBZ2UgYmV0d2VlbiAyMOKAkzY0ICAgICAgICAgICAgICAgICAgfCBudW0gICAgICB8DQp8IGdlbmRlciAgICAgICAgICAgICAgICAgICAgfCBGLCBNICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgY2hyICAgICAgfA0KfCBoZWlnaHRfY20gICAgICAgICAgICAgICAgIHwgSGVpZ2h0IGluIGNlbnRpbWV0ZXJzICAgICAgICAgICAgICB8IG51bSAgICAgIHwNCnwgd2VpZ2h0X2tnICAgICAgICAgICAgICAgICB8IFdlaWdodCBpbiBraWxvZ3JhbXMgICAgICAgICAgICAgICAgfCBudW0gICAgICB8DQp8IGJvZHkuZmF0Xy4gICAgICAgICAgICAgICAgfCBCb2R5IGZhdCBpbmRleCAgICAgICAgICAgICAgICAgICAgIHwgbnVtICAgICAgfA0KfCBkaWFzdG9saWMgICAgICAgICAgICAgICAgIHwgQmxvb2QgcHJlc3N1cmUgKGRpYXN0b2xpYykgICAgICAgICB8IG51bSAgICAgIHwNCnwgc3lzdG9saWMgICAgICAgICAgICAgICAgICB8IEJsb29kIHByZXNzdXJlIChzeXN0b2xpYykgICAgICAgICAgfCBudW0gICAgICB8DQp8IGdyaXBGb3JjZSAgICAgICAgICAgICAgICAgfCBNZWFzdXJlZCBpbiBrZyAgICAgICAgICAgICAgICAgICAgIHwgbnVtICAgICAgfA0KfCBzaXQuYW5kLmJlbmQuZm9yd2FyZF9jbSAgIHwgTnVtYmVyIG9mIHNpdCAmIGJlbmRzIHBlciBtaW51dGUgICB8IG51bSAgICAgIHwNCnwgc2l0LnVwcy5jb3VudHMgICAgICAgICAgICB8IE51bWJlciBvZiBzaXQtdXBzIGluIDIgbWludXRlcyAgICAgfCBudW0gICAgICB8DQp8IGJyb2FkLmp1bXBfY20gICAgICAgICAgICAgfCBOdW1iZXIgb2YgYnJvYWQganVtcHMgICAgICAgICAgICAgIHwgbnVtICAgICAgfA0KfCBjbGFzcyAgICAgICAgICAgICAgICAgICAgIHwgRml0bmVzcyBsZXZlbCAoQTogYmVzdCwgRDogbG93ZXN0KSB8IGNociAgICAgIHwNCg0KDQojIyBDaGVja2luZyBGb3IgTWlzc2luZyBkYXRhDQoNCk5vIG1pc3NpbmcgZGF0YSBpbiB0aGUgZGF0YWJhc2UNCg0KYGBge3IgZWNobz1GQUxTRX0NCnN1bSAoaXMubmEoZGF0YSkpDQpgYGANCg0KIyMgRGF0YSBEaXN0cmlidXRpb24gJiBPdXRsaWVycw0KDQpvdmVyYWxsIGhpZ2ggbGV2ZWwgbG9vayBhdCB0aGUgdGhlIGRhdGEgZGlzdHJpYnV0aW9uLCBkb2VzIG5vdCBzaG93IGFueSBjb25jZXJucyB3aXRoIHRoZSBkYXRhLiB3aGlsZSB3ZSB3aWxsIGJlIGxvb2tpbmcgYXQgZWFjaCB2YXJpYWJsZXMgaW4gZGV0YWlscyBsYXRlIG9uLCBsb29raW5nIGF0IHRoZSBiZWxvdyB0YWJsZXMsIHNob3dzIHRoYXQgbWFsZXMgcGFydGljaXBhbnQgYXJlIGFsbW9zdCBkb3VibGUgb2YgdGhlIGZlbWFsZXMgcGFydGljaXBhbnQuIEFsc28sIHdlIGNhbiBzZWUgbWFqb3JpdHkgYXJlIGluIHRoZSBhZ2Ugb2YgMjAtMzAgd2hpY2ggZXhwZWN0ZWQgc2luY2UgdGhpcyBhdGhsZXRpYyBwZXJmb3JtYW5jZSBkYXRhLiB3ZSBjYW4gYWxzbyBzZWUgdGhhdCBzaXQtdXBzLCBib2FyZCBqdW1wcyBhbmQgZ3JpcCBmb3JjZSBhcmUgYWxtb3N0IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHdoaWxlIHNpdCBhbmQgYmVuZCBjb25jZW50cmF0ZWQgYmV0d2VlbiA0MC01MC4NCg0KYGBge3J9DQoNCmxheW91dChtYXRyaXgoMToxMiwgbnJvdyA9IDMpLCB3aWR0aHMgPSBjKDEsIDEsIDEsIDEpKQ0KIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUgR2VuZGVyDQpnZW5kZXJfY291bnRzIDwtIHRhYmxlKGRhdGEkZ2VuZGVyKQ0KDQojIE1ha2UgdGhlIGJhcnBsb3QgYW5kIGNhcHR1cmUgdGhlIG1pZHBvaW50cyBvZiBiYXJzDQpicCA8LSBiYXJwbG90KGdlbmRlcl9jb3VudHMsDQogICAgICAgICAgICAgIG1haW4gPSAiRGlzdHJpYnV0aW8gYnkgR2VuZGVyIiwNCiAgICAgICAgICAgICAgY29sID0gInNreWJsdWUiLA0KICAgICAgICAgICAgICB4bGFiID0gIkdlbmRlciIsDQogICAgICAgICAgICAgIHlsaW0gPSBjKDAsIG1heChnZW5kZXJfY291bnRzKSAqIDEuNCkpICAjIEFkZCBhIGxpdHRsZSBzcGFjZSBmb3IgdGhlIGxhYmVscw0KDQojIEFkZCB0aGUgdGV4dCBsYWJlbHMgKGNvdW50cykNCnRleHQoeCA9IGJwLCB5ID0gZ2VuZGVyX2NvdW50cywgbGFiZWxzID0gZ2VuZGVyX2NvdW50cywgcG9zID0gMywgY2V4ID0gMC45KQ0KDQoNCiMgQ3JlYXRlIGEgZnJlcXVlbmN5IHRhYmxlDQpjbGFzc19jb3VudHMgPC0gdGFibGUoZGF0YSRjbGFzcykNCg0KIyBNYWtlIHRoZSBiYXJwbG90IGFuZCBjYXB0dXJlIHRoZSBtaWRwb2ludHMgb2YgYmFycw0KYnAgPC0gYmFycGxvdChjbGFzc19jb3VudHMsDQogICAgICAgICAgICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIGJ5IENsYXNzIiwNCiAgICAgICAgICAgICAgY29sID0gInNreWJsdWUiLA0KICAgICAgICAgICAgICB4bGFiID0gIkNsYXNzIiwNCiAgICAgICAgICAgICAgeWxpbSA9IGMoMCwgbWF4KGNsYXNzX2NvdW50cykgKiAxLjQpKSAgIyBBZGQgYSBsaXR0bGUgc3BhY2UgZm9yIHRoZSBsYWJlbHMNCg0KIyBBZGQgdGhlIHRleHQgbGFiZWxzIChjb3VudHMpDQp0ZXh0KHggPSBicCwgeSA9IGNsYXNzX2NvdW50cywgbGFiZWxzID0gY2xhc3NfY291bnRzLCBwb3MgPSAzLCBjZXggPSAwLjkpDQoNCmhpc3QoZGF0YSRib2R5LmZhdCwgbWFpbj0iQmR5IEZhdCIsIGNvbD0ic2t5Ymx1ZSIsIHhsYWI9IlZhbHVlIiwgYnJlYWtzPTYpDQpoaXN0KGRhdGEkd2VpZ2h0X2tnLCBtYWluPSJXZWlnaHQgS0ciLCBjb2w9InNreWJsdWUiLCB4bGFiPSJWYWx1ZSIsIGJyZWFrcz02KQ0KaGlzdChkYXRhJGhlaWdodF9jbSwgbWFpbj0iSGVpZ2h0IENNIiwgY29sPSJza3libHVlIiwgeGxhYj0iVmFsdWUiLCBicmVha3M9NikNCmhpc3QoZGF0YSRkaWFzdG9saWMsIG1haW49IkRpYXN0b2xpYyIsIGNvbD0ic2t5Ymx1ZSIsIHhsYWI9IlZhbHVlIiwgYnJlYWtzPTYpDQpoaXN0KGRhdGEkc3lzdG9saWMsIG1haW49IlN5c3l0b2xpYyIsIGNvbD0ic2t5Ymx1ZSIsIHhsYWI9IlZhbHVlIiwgYnJlYWtzPTYpDQoNCmhpc3QoZGF0YSRncmlwRm9yY2UsIG1haW49ImdyaXBGb3JjZSIsIGNvbD0ic2t5Ymx1ZSIsIHhsYWI9IlZhbHVlIiwgYnJlYWtzPTYpDQpoaXN0KGRhdGEkc2l0LmFuZC5iZW5kLmZvcndhcmRfY20sIG1haW49InNpdC5hbmQuYmVuZC5mb3J3YXJkX2NtIiwgY29sPSJza3libHVlIiwgeGxhYj0iVmFsdWUiLCBicmVha3M9NikNCmhpc3QoZGF0YSRzaXQudXBzLmNvdW50cywgbWFpbj0ic2l0LnVwcy5jb3VudHMiLCBjb2w9InNreWJsdWUiLCB4bGFiPSJWYWx1ZSIsIGJyZWFrcz02KQ0KaGlzdChkYXRhJGJyb2FkLmp1bXBfY20sIG1haW49ImJyb2FkLmp1bXBfY20iLCBjb2w9InNreWJsdWUiLCB4bGFiPSJWYWx1ZSIsIGJyZWFrcz02KQ0KaGlzdChkYXRhJGFnZSwgbWFpbj0iYWdlIiwgY29sPSJza3libHVlIiwgeGxhYj0iVmFsdWUiLCBicmVha3M9NikNCg0KYGBgDQoNCvCfn6YgR2VuZGVyOiBEYXRhIG1vcmUgc2tld2VkIHRvd2FyZCBoaWdoZXIgbWFsZXMgcGFydGljaXBhbnRzIHdpdGggbWFsZXMgLyBmZW1hbGVzIGRpc3RyaWJ1dGlvbiBhdCAyLzMgdG8gMS8zLg0KDQrwn5+mIENsYXNzOiBEYXRhIGVxdWFsbHkgZGlzdHJpYnV0ZWQgYWNyb3NzIHRoZSA0IGNsYXNzZXMuDQoNCvCfn6YgQWdlOiBEYXRhIHNrZXdlZCB0b3dhcmQgeW91bmdlciBwYXJ0aWNpcGFudCB3aXRoLCBhbG1vc3QgNDUlIGluIHRoZSBhZ2UgZ3JvdXAgMjAtMzAuDQoNCmBgYHtyfQ0KIyBTZXQgdXAgbGF5b3V0OiBtYXRyaXggb2Ygb25lIHJvdywgdHdvIGNvbHVtbnMgd2l0aCB3aWR0aHMgMjoxDQpsYXlvdXQobWF0cml4KDE6MywgbnJvdyA9IDEpLCB3aWR0aHMgPSBjKDEsIDEsIDEpKQ0KDQojIENyZWF0ZSBhIGZyZXF1ZW5jeSB0YWJsZSBHZW5kZXINCmdlbmRlcl9jb3VudHMgPC0gdGFibGUoZGF0YSRnZW5kZXIpDQoNCiMgTWFrZSB0aGUgYmFycGxvdCBhbmQgY2FwdHVyZSB0aGUgbWlkcG9pbnRzIG9mIGJhcnMNCmJwIDwtIGJhcnBsb3QoZ2VuZGVyX2NvdW50cywNCiAgICAgICAgICAgICAgbWFpbiA9ICJEaXN0cmlidXRpbyBieSBHZW5kZXIiLA0KICAgICAgICAgICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgICAgICAgICAgIHhsYWIgPSAiR2VuZGVyIiwNCiAgICAgICAgICAgICAgeWxpbSA9IGMoMCwgbWF4KGdlbmRlcl9jb3VudHMpICogMS4xKSkgICMgQWRkIGEgbGl0dGxlIHNwYWNlIGZvciB0aGUgbGFiZWxzDQoNCiMgQWRkIHRoZSB0ZXh0IGxhYmVscyAoY291bnRzKQ0KdGV4dCh4ID0gYnAsIHkgPSBnZW5kZXJfY291bnRzLCBsYWJlbHMgPSBnZW5kZXJfY291bnRzLCBwb3MgPSAzLCBjZXggPSAwLjkpDQoNCg0KIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUNCmNsYXNzX2NvdW50cyA8LSB0YWJsZShkYXRhJGNsYXNzKQ0KDQojIE1ha2UgdGhlIGJhcnBsb3QgYW5kIGNhcHR1cmUgdGhlIG1pZHBvaW50cyBvZiBiYXJzDQpicCA8LSBiYXJwbG90KGNsYXNzX2NvdW50cywNCiAgICAgICAgICAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gYnkgQ2xhc3MiLA0KICAgICAgICAgICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgICAgICAgICAgIHhsYWIgPSAiQ2xhc3MiLA0KICAgICAgICAgICAgICB5bGltID0gYygwLCBtYXgoY2xhc3NfY291bnRzKSAqIDEuMSkpICAjIEFkZCBhIGxpdHRsZSBzcGFjZSBmb3IgdGhlIGxhYmVscw0KDQojIEFkZCB0aGUgdGV4dCBsYWJlbHMgKGNvdW50cykNCnRleHQoeCA9IGJwLCB5ID0gY2xhc3NfY291bnRzLCBsYWJlbHMgPSBjbGFzc19jb3VudHMsIHBvcyA9IDMsIGNleCA9IDAuOSkNCg0KIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUNCmFnZV9jb3VudHMgPC0gdGFibGUoZGF0YSRhZ2UpDQoNCmhpc3QoZGF0YSRhZ2UsIG1haW49IkRpc3RyaWJ1dGlvbiBieSBBZ2UiLCBjb2w9InNreWJsdWUiLCB4bGFiPSJBZ2UiLCBicmVha3M9NSkNCg0KYGBgDQoNCvCfn6UgQm9keSBGYXQ6IFR5cGljYWwgcmFuZ2U6IH4xNSUgdG8gMzUlIHdpdGggcHJlc2VuY2Ugb2Ygb3V0bGllcnMgIGFib3ZlIDQwJSBhbmQgc29tZSBldmVuIGFib3ZlIDc1JS4gVGhlc2Ugb3V0bGllcnMgY291bGQgcmVmbGVjdCBpbmRpdmlkdWFscyB3aXRoIG9iZXNpdHkgb3IgcG9zc2libHkgZGF0YSBlbnRyeSBlcnJvcnPigJR3b3J0aCBjaGVja2luZy4NCg0K8J+fpiBXZWlnaHQgKGtnKTogVHlwaWNhbCByYW5nZTogfjU1IGtnIHRvIDg1IGtnLiB0aGUgZGF0YSBoYXZlIG91dGxpZXJzIG9uIGJvdGggZW5kcyB3aXRoIGxvdHMgb2YgaW5kaXZpZHVhbHMgYWJvdmUgMTAwIGtnIOKAlCBleHBlY3RlZCBpbiBwb3B1bGF0aW9ucyB3aXRoIGhpZ2hlciBCTUkgYW5kIGEgZmV3IGJlbG93IDQwIGtnIOKAlCBtaWdodCBiZSB2ZXJ5IGxlYW4gaW5kaXZpZHVhbHMgb3IgeW91bmcgcGFydGljaXBhbnRzDQoNCkNvbnNpZGVyIHN0cmF0aWZ5aW5nIGJ5IGdlbmRlciBvciBhZ2UgaWYgdGhlc2UgdmFsdWVzIHNlZW0gaW5jb25zaXN0ZW50DQoNCvCfn6ogSGVpZ2h0IChDTSk6IFR5cGljYWwgcmFuZ2U6IH4xNjAgdG8gMTgwIGNtLiBBIGZldyB2YWx1ZXMgYmVsb3cgMTQwIGNtIOKAlCBUaGlzIGNvdWxkIGJlIGZlbWFsZXMgb3Igb3V0bGllcnMgKHBvc3NpYmx5IGV2ZW4gZGF0YSBlcnJvcikgYW5kIGEgY291cGxlIG5lYXIgMTk1IGNtLg0KDQrwn5+pIERpYXN0b2xpYyAobW0gSGcpOiBUeXBpY2FsIHJhbmdlOiB+NjUgdG8gOTAgbW0gSGcuIG91dGxpZXJzIHdpdGggdmFsdWVzIGJlbG93IDMwIGFuZCBhYm92ZSAxMjAg4oCUIHBvdGVudGlhbGx5IHNlcmlvdXMgbWVkaWNhbCBjb25kaXRpb25zIG9yIGRhdGEgZW50cnkgZXJyb3JzLiBEaWFzdG9saWMgdmFsdWVzIGJlbG93IDMwIGFyZSBxdWl0ZSByYXJlIHBoeXNpb2xvZ2ljYWxseQ0KDQrwn5+lIFN5c3RvbGljIChtbSBIZyk6IFR5cGljYWwgcmFuZ2U6IH4xMDAgdG8gMTQ1IG1tIEhnIHdpdGggc2V2ZXJhbCB2ZXJ5IGxvdyB2YWx1ZXMgKDwgNTAgbW0gSGcpIGFuZCB2ZXJ5IGhpZ2ggdmFsdWVzICg+IDE4MCBtbSBIZykuIFRoZXNlIG1heSBpbmRpY2F0ZSBoeXBvdGVuc2l2ZSBvciBoeXBlcnRlbnNpdmUgcGF0aWVudHMsIG9yIGNvdWxkIGJlIGVycm9ycyBpbiBkYXRhIGNhcHR1cmUNCg0KDQpgYGB7cn0NCg0KbGF5b3V0KG1hdHJpeCgxOjUsIG5yb3cgPSAxKSwgd2lkdGhzID0gYygxLCAxLCAxLCAxLCAxKSkNCg0KYm94cGxvdChkYXRhJGJvZHkuZmF0Xy4sIG1haW49IkJvZHkgRmF0IiwgY29sPSJ0b21hdG8iKQ0KYm94cGxvdChkYXRhJHdlaWdodF9rZywgbWFpbj0iV2VpZ2h0IChrZykiLCBjb2w9InNreWJsdWUiKQ0KYm94cGxvdChkYXRhJGhlaWdodF9jbSwgbWFpbj0iSGVpZ2h0IChDTSkiLCBjb2w9InB1cnBsZTEiKQ0KYm94cGxvdChkYXRhJGRpYXN0b2xpYywgbWFpbj0iRGlhc3RvbGljIiwgY29sPSJjeWFuNCIpDQpib3hwbG90KGRhdGEkc3lzdG9saWMsIG1haW49InN5c3RvbGljIiwgY29sPSJicm93bjIiKQ0KDQpgYGANCg0K8J+fpSBHcmlwIEZvcmNlIDogTWVkaWFuIGFyb3VuZCAzOOKAkzQwIHdpdGggdGhlIGRpc3RyaWJ1dGlvbiBiZWluZyBtb2RlcmF0ZWx5IHNwcmVhZCAobm90IHNrZXdlZCkuIEEgZmV3IG91dGxpZXJzIGJlbG93IChleHRyZW1lbHkgbG93IGdyaXAgZm9yY2VzLCBwb3NzaWJseSBlcnJvcnMgb3IgdHJ1ZSB3ZWFrIG1lYXN1cmVtZW50cykuDQoNCvCfn6ogU2l0ICYgQmVuZDogVmVyeSBuYXJyb3cgZGlzdHJpYnV0aW9uLCBidXQgc29tZSBleHRyZW1lIG91dGxpZXJzIHdheSBhYm92ZSAyMDAgYW5kIGEgZmV3IGxvdyBvdXRsaWVycywgcG9zc2libHkgZHVlIHRvIHN0aWZmIGluZGl2aWR1YWxzIG9yIG1pc3JlcG9ydGluZy4gUG9zc2libHkgYSBza2V3ZWQgZGlzdHJpYnV0aW9uLg0KDQrwn5+mIFNpdC11cHMgQ291bnQ6IE1lZGlhbiBhcm91bmQgNDAuIEZhaXJseSBzeW1tZXRyaWMuIEEgZmV3IGxvdyBvdXRsaWVycyAoc29tZW9uZSBtYXliZSBzdG9wcGVkIGVhcmx5IG9yIGhhZCBhbiBpbmp1cnk/KS4NCg0K8J+fqSBCcm9hZCBKdW1wIChDTSk6IE1lZGlhbiBuZWFyIDIwMCB3aXRoIHdpZGUgZGlzdHJpYnV0aW9uLi5RdWl0ZSBhIGZldyBvdXRsaWVycyBiZWxvdyAxMDAsIGluZGljYXRpbmcgZWl0aGVyIGxvdyBwZXJmb3JtYW5jZSBvciBtZWFzdXJlbWVudCBpc3N1ZXMuDQoNCg0K8J+fpyBBZ2U6IE1lZGlhbiBhcm91bmQgMzAgYW5kIGRpc3RyaWJ1dGlvbiBza2V3ZWQgc2xpZ2h0bHkgcmlnaHQuIE5vIHB1dGxpZXJzIG9ic2VydmVkLg0KDQpgYGB7cn0NCg0KbGF5b3V0KG1hdHJpeCgxOjUsIG5yb3cgPSAxKSwgd2lkdGhzID0gYygxLCAxLCAxLCAxLCAxKSkNCg0KYm94cGxvdChkYXRhJGdyaXBGb3JjZSwgbWFpbj0iR3JpcCBGb3JjZSIsIGNvbD0iYnJvd24yIikNCmJveHBsb3QoZGF0YSRzaXQuYW5kLmJlbmQuZm9yd2FyZF9jbSwgbWFpbj0iU2l0ICYgQmVuZCIsIGNvbD0icHVycGxlMSIpDQpib3hwbG90KGRhdGEkc2l0LnVwcy5jb3VudHMsIG1haW49IlNpdC11cHMgQ291bnQiLCBjb2w9InNreWJsdWUiKQ0KYm94cGxvdChkYXRhJGJyb2FkLmp1bXBfY20sIG1haW49IkJyb2FkIEp1bXAgKENNKSIsIGNvbD0iY3lhbjQiKQ0KYm94cGxvdChkYXRhJGFnZSwgbWFpbj0iYWdlIiwgY29sPSJ0b21hdG8iKQ0KDQpgYGANCg0KIyMgRXZhbHVhdGluZyBXZWlnaHQgYnkgR2VuZGVyDQoNCvCfn6cgRmVtYWxlcyAoRik6IEFzIGV4cGVjdGVkLCBtZWRpYW4gd2VpZ2h0IGlzIGxvd2VyIHRoYW4gZm9yIG1hbGVzIHdpdGggTWFqb3JpdHkgbGllIHJvdWdobHkgYmV0d2VlbiB+NTAga2cgYW5kIDcwIGtnLg0KDQpUaGVyZSdzIGEgd2lkZXIgc3ByZWFkIG9mIG91dGxpZXJzIG9uIHRoZSBoaWdoIGVuZCAoc29tZSB3b21lbiBvdmVyIDEwMCBrZyBhbmQgYSBmZXcgbG93LXdlaWdodCBvdXRsaWVycyBiZWxvdyA0NSBrZy4NCg0K8J+fpiBNYWxlcyAoTSk6IE1lZGlhbiB3ZWlnaHQgYXJvdW5kIDcw4oCTNzUga2cgd2l0aCB0aGUgbWlkZGxlIDUwJSBvZiBtYWxlIHdlaWdodHMgc3BhbiB+NjUga2cgdG8gOTAga2cuIE1hbGVzIGhhdmUgbW9yZSBvdXRsaWVycyBvdmVyYWxsIOKAlCBib3RoIGxvdyBhbmQgaGlnaCBlbmRzLg0KDQpPdmVyYWxsLCB3ZWlnaHQgaXMgcmlnaHQtc2tld2VkIGZvciBib3RoIGdyb3VwcyDigJQgbWFueSBvdXRsaWVycyBhcHBlYXIgb24gdGhlIGhpZ2ggZW5kIG9mIHRoZSB3ZWlnaHQgc2NhbGUsIHN1Z2dlc3Rpbmcgc29tZSBoZWF2aWVyIGluZGl2aWR1YWxzIHB1bGwgdGhlIG1lYW4gdXAuIFdlIHdvdWxkIG5lZWQgdG8gY2hlY2sgdGhlIG91dGxpZXJzIGZvciBwb3RlbnRpYWwgZGF0YSBlbnRyeSBlcnJvcnMgb3IgdmFsaWQgZXh0cmVtZSBjYXNlcy4NCg0KYGBge3J9DQoNCnBsb3RfbHkoZGF0YSwgDQogICAgICAgIHggPSB+d2VpZ2h0X2tnLCANCiAgICAgICAgeSA9IH5nZW5kZXIsIA0KICAgICAgICB0eXBlID0gImJveCIsIA0KICAgICAgICBjb2xvciA9IH5nZW5kZXIsDQogICAgICAgICAgICAgICAgY29sb3JzID0gYygiY3lhbjQiLCAicmVkIiksDQogICAgICAgICBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCANCiAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAnYmxhY2snKSwNCiAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdibGFjaycsIHNpemUgPSA0KSkNCiAgICAgICAgICAjIEN1c3RvbWl6ZSBjb2xvcnMgaGVyZQ0KDQpgYGANCg0KIyBQcmVkaWN0aW5nIE51bWJlciBvZiBTaXQgdXBzDQoNClRvIGFjaGlldmUgdGhhdCwgbmVlZCB0byBidWlsZCBSZWdyZXNzaW9uIFRyZWUuIFdlIHVzZSB0aGUgZGF0YSB3ZSBoYXZlIHRvIHRyYWluIGFuIGFsZ29yaXRobSBmb3IgcHJlZGljdGluZyBudW1iZXIgb2YgU2l0LXVwIGFuIGF0aGxldGUgY2FuIHBlcmZvcm0uIA0KDQpUaGlzIHByb2Nlc3MgcmVxdWlyZXMgbXVsdGlwbGUgc3RlcHMgYXMgZm9sbG93czoNCg0KIyMgVHJlZSBJbmR1Y3Rpb246DQoNCmluIHRoaXMgc3RlcCB3ZSBidWlsZCB0aGUgbWFpbiB0cmVlIHV0aWxpemluZyB0aGUgdHJhaW5pbmcgZGF0YXNldCB3aGVyZSB3ZSBldmFsdWF0ZSBhbGwgcG9zc2libGUgZmVhdHVycyBmb3Igc3BsaXRpbmcgYWZ0ZXIgZGVjaWRpbmcgc3RvcHBpbmcgcnVsZXMgdGhhdCB3b3VsZCB0ZWxsIG91ciBhbGdvcml0aG0gd2hlbiB0byBzdG9wLiBJbiB0aGlzIHByb2Nlc3MgaGVyZSwgd2UgdXNlZCBkZWZhdWx0IHJ1bGVzIHN0b3BwaW5nIHJ1bGVzIHdpdGhvdXQgdHJlZSBwcnVuaW5nIGFzIGZvbGxvd3M6DQogKyBNaW4gb2JzZXJ2YXRpb25zIGluIGFueSBub2RlIHRvIHBlcmZvcm0gYSBzcGxpdCBpcyAyMA0KICsgTWluIG9ic2VydmF0aW9ucyBpbiB0ZXJtaW5hbCBub2RlIDcNCiArIE1heCBkZXB0aCBvZiB0aGUgdHJlZSA1IGxldmVscy4NCiANCiBUaGUgYWJvdmUgc3RvcHBpbmcgcnVsZXMgd2UgYXJyaXZlZCBhdCBhZnRlciBhdHRlbXB0aW5nIG11bHRpcGxlIGNvbWJpbmF0aW9ucyBhbmQgdmFsdWVzLg0KDQpgYGB7cn0NCg0KIyBTZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQpzZXQuc2VlZCgxMjMpDQoNCg0KIyBTcGxpdCBkYXRhIGludG8gdHJhaW5pbmcgKDcwJSkgYW5kIHRlc3QgKDMwJSkgc2V0cw0KdHJhaW4uaW5kZXggPC0gc2FtcGxlKDE6bnJvdyhkYXRhKSwgc2l6ZSA9IDAuNyAqIG5yb3coZGF0YSkpDQp0cmFpbi5kYXRhIDwtIGRhdGFbdHJhaW4uaW5kZXgsIF0NCnRlc3QuZGF0YSA8LSBkYXRhWy10cmFpbi5pbmRleCwgXQ0KDQojIDEuIFRyZWUgSW5kdWN0aW9uICYgMi4gU3BsaXR0aW5nIENyaXRlcmlhDQojIEJ1aWxkIHRoZSBpbml0aWFsIHJlZ3Jlc3Npb24gdHJlZSB1c2luZyBycGFydA0KdHJlZS5tb2RlbCA8LSBycGFydChzaXQudXBzLmNvdW50cyB+IC4sIA0KICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImFub3ZhIiwgICAgICMgRm9yIHJlZ3Jlc3Npb24NCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgbWluc3BsaXQgPSAyMCwgICAgIyAzLiBTdG9wcGluZyBydWxlOiBtaW4gb2JzZXJ2YXRpb25zIHRvIHNwbGl0DQogICAgICAgICAgICAgICAgICAgICAgbWluYnVja2V0ID0gMTAsICAgICMgTWluIG9ic2VydmF0aW9ucyBpbiB0ZXJtaW5hbCBub2RlDQogICAgICAgICAgICAgICAgICAgICAgY3AgPSBzZXEoMCwgMC4wNSwgMjApLCAjIENvbXBsZXhpdHkgcGFyYW1ldGVyDQogICAgICAgICAgICAgICAgICAgICAgbWF4ZGVwdGggPSA2ICAgICAgIyBNYXhpbXVtIHRyZWUgZGVwdGgNCiAgICAgICAgICAgICAgICAgICAgKSkNCg0KIyBWaXN1YWxpemUgdGhlIHVucHJ1bmVkIHRyZWUNCnJwYXJ0LnBsb3QodHJlZS5tb2RlbCwgbWFpbiA9ICJJbml0aWFsIFJlZ3Jlc3Npb24gVHJlZSIpDQoNCmBgYA0KDQpUaGUgaW5pdGlhbCB0cmVlIGNvbnRhaW5zIGEgbG9uZyBsaXN0IG9mIGRpZmZlcmVudCBwaWVjZXMgb2YgaW5mb3JtYXRpb24gdGhhdCBjYW4gYmUgdXNlZCB0byBpbXByb3ZlIHRoZSBpbml0aWFsIHRyZWUgbW9kZWwuIEluIG9yZGVyIHRvIGltcHJvdmUgdGhlIHRyZWUsIHdlZSBsb29rIGF0IHRoZSBtb2RlbCBjb21wbGV4aXR5IHBhcmFtZXRlciBjcCBhbmQgcmVsYXRlZCBlcnJvcnMgYW5kIGFwcHJvcHJpYXRlbHkgcHJ1bmUgdGhlIGluaXRpYWwgdHJlZS4gDQoNCiMjIFBydW5pbmcgUHJvY2Vzcw0KDQpUaGUgYmVsb3cgY29tcGxleGl0eSB0YWJsZSBzaG93cyBrZXkgaW5mb3JtYXRpb24gd2UgbmVlZCBmb3Igb3VyIHRyZWUgcHJ1bmluZyBiYXNlZCBvbiBjcm9zcy12YWxpZGF0aW9uOg0KDQogKyBDb21wbGV4aXR5IHBhcmFtZXRlciB2YWx1ZXM6IFBlbmFsdHkgdGVybSB0aGF0IGJhbGFuY2VzIHRyZWUgY29tcGxleGl0eSB3aXRoIGZpdCBxdWFsaXR5DQogKyBOdW1iZXIgb2Ygc3BsaXRzIGluIHRoZSBUcmVlIChuc3BsaXQpDQogKyBSZWxhdGl2ZSBlcnJvciAocmVsIGVycm9yKSBDYWxjdWxhdGVkIGFzOiBFcnJvcihjdXJyZW50X3RyZWUpL0Vycm9yKHJvb3Rfbm9kZSkNCiArIENyb3NzLXZhbGlkYXRlZCBlcnJvciAoeGVycm9yKQ0KICsgU3RhbmRhcmQgZXJyb3Igb2YgdGhlIGNyb3NzLXZhbGlkYXRlZCBlcnJvciAoeHN0ZCkNCg0KYGBge3J9DQoNCiMgRXhhbWluZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMNCnBhbmRlcih0cmVlLm1vZGVsJGNwdGFibGUpDQoNCmBgYA0KDQpmcm9tIHRoZSBhYm92ZSwgd2UgY2FuIHNlZSB0aGF0IG1pbiBDcm9zcyB2YWxpZGF0aW9uIGVycm9yIGlzIDAuMzE3OCB3aXRoIG51bWJlciBvZiBzcGxpdHMgb2YgMjggYWJkIGNwIG9mIDAuMDAwNzQ3OC4gDQoNCiMjIFNlbGVjdGluZyBPcHRpbWFsIFRyZWUgU2l6ZQ0KDQpTbWFsbGVzdCBuc3BsaXQgd2hlcmUgeGVycm9yIGlzIHdpdGhpbiAxIHN0YW5kYXJkIGVycm9yICh4c3RkKSBvZiB0aGUgbWluaW11bSBDUC4gaW4gdGhlIHRhYmxlIHJlcHJlc2VudCAwLjMxOTYgd2l0aCBuc3BsaXQgb2YgMjUuIFdlIG5lZWQgdG8gaWRlbnRpZnkgdGhlIGxhcmdlc3QgQ1Agd2hlcmUgeGVycm9yIGlzIHdpdGhpbiAxIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBtaW5pbXVtICh0byBiYWxhbmNlIHNpbXBsaWNpdHkgYW5kIGFjY3VyYWN5KS4NCg0KYGBge3J9DQoNCnBsb3RjcCh0cmVlLm1vZGVsKQ0KDQpgYGANCg0KQXMgbWVudGlvbmVkIGVhcmxpZXIsIHdlIHNlbGVjdCB0aGUgbGFyZ2VzdCBjcCB3aGVyZSB4ZXJyb3IgaXMgd2l0aGluIDEgc3RhbmRhcmQgZXJyb3Igb2YgdGhlIG1pbmltdW0gKHRvIGJhbGFuY2Ugc2ltcGxpY2l0eSBhbmQgYWNjdXJhY3kpLiBiYXNlZCBvbiB0aGlzIG91ciBiZXN0IHBvc3NpYmxlIHRyZWUgd291bGQgaGF2ZSAyNSBub2RlcyBhbmQgY29tcGxleGl0eSANCg0KDQoNCmBgYHtyfQ0KDQpjcC50YWJsZSA8LSB0cmVlLm1vZGVsJGNwdGFibGUNCg0KIyMgSWRlbnRpZnkgdGhlIG1pbmltdW0gYHhlcnJvcmAgYW5kIGl0cyBgY3BgLg0KbWluLnhlcnJvciA8LSBtaW4oY3AudGFibGVbLCAieGVycm9yIl0pDQptaW4uY3Aucm93IDwtIHdoaWNoLm1pbihjcC50YWJsZVssICJ4ZXJyb3IiXSkNCm1pbi5jcCA8LSBjcC50YWJsZVttaW4uY3Aucm93LCAiQ1AiXQ0KDQojIyBHZXQgdGhlIHN0YW5kYXJkIGVycm9yIChgeHN0ZGApIG9mIHRoZSBtaW5pbXVtIGB4ZXJyb3JgDQp4ZXJyb3Iuc3RkIDwtIGNwLnRhYmxlW21pbi5jcC5yb3csICJ4c3RkIl0NCnRocmVzaG9sZCA8LSBtaW4ueGVycm9yICsgeGVycm9yLnN0ZCAgIyBVcHBlciBib3VuZCAoMSBTRSBydWxlKQ0KDQojIyBGaW5kIHRoZSBzaW1wbGVzdCB0cmVlIChgY3BgKSBXaGVyZSBgeGVycm9yIGxlc3MgdGhhbiBvciBlcXVhbCB0byBUaHJlc2hvbGRgLg0KYmVzdC5jcC5yb3cgPC0gd2hpY2goY3AudGFibGVbLCAieGVycm9yIl0gPD0gdGhyZXNob2xkKVsxXSAgIyBGaXJzdCByb3cgbWVldGluZyBjcml0ZXJpYQ0KYmVzdC5jcCA8LSBjcC50YWJsZVtiZXN0LmNwLnJvdywgIkNQIl0NCg0KIyMgVHdvIGRpZmZlcmVudCB0cmVlczogYmVzdCBDUCB2cyBtaW5pbXVtIENQDQpwcnVuZWQudHJlZS5iZXN0LmNwIDwtIHBydW5lKHRyZWUubW9kZWwsIGNwID0gYmVzdC5jcCkNCnBydW5lZC50cmVlLm1pbi5jcCA8LSBwcnVuZSh0cmVlLm1vZGVsLCBjcCA9IG1pbi5jcCkNCg0KYGBgDQoNClRoZSBhYm92ZSB0cmVlIHNob3dzIHRoYXQgb3VyIGJlc3QgY3AgPSAwLjAwMTQgd2hlcmUgdGhlIHRyZWUgaGFzIDI2IHNwbGl0cyBhbmQgY3Jvc3MgdmFsaWRhdGlvbiBlcnJvciAwLjMxOTYNCg0KYGBge3J9DQojIFZpc3VhbGl6ZSB0aGUgcHJ1bmVkIHRyZWU6IGJlc3QgQ1ANCnJwYXJ0LnBsb3QocHJ1bmVkLnRyZWUuYmVzdC5jcCwgbWFpbiA9IHBhc3RlKCJQcnVuZWQgVHJlZSAoQmVzdCBDUCk6IGNwID0gIiwgcm91bmQoYmVzdC5jcCw0KSkpDQoNCmBgYA0KDQpUaGUgYWJvdmUgbWluIGNwIHRyZWUgc2hvd3MgMzAgc3BsaXRzIGJ1dCB3aXRoIGhpZ2hlciBjb21wbGV4aXR5IGFuZCBsb3dlciBpbmNyZWFzZSBpbiB0cmVlIHN0cmVuZ3RoIA0KDQoNCmBgYHtyfQ0KDQojIFZpc3VhbGl6ZSB0aGUgcHJ1bmVkIHRyZWU6IG1pbmltdW0gQ1ANCnJwYXJ0LnBsb3QocHJ1bmVkLnRyZWUubWluLmNwLCBtYWluID0gcGFzdGUoIlBydW5lZCBUcmVlIChNaW5pbXVtIENQKTogY3AgPSAiLCByb3VuZChtaW4uY3AsNCkpKQ0KDQpgYGANCg0KIyMgQnVpbGRpbmcgdGhlIGxpbmVhciByZWdlc3Npb24gbW9kZWwNCg0KTmV4dCwgd2UgdXNlIHRoZSBmaW5hbCBwcnVuZWQgcmVncmVzc2lvbiB0cmVlIHRvIG1ha2UgcHJlZGljdGlvbnMuIFNpbmNlIG9ubHkgZml2ZSBmZWF0dXJlcyAiYWJvZHkuZmF0Xy4iICsgImdlbmRlciIgKyAiYnJvYWQuanVtcF9jbSIgKyAiY2xhc3MiICsgImFnZSIgd2VyZSB1c2VkIGluIHRoZSBhbGdvcml0aG0uIA0KDQpBcyBuZXh0IHN0ZXA6IHdlIHVzZSB0aGUgcHJ1bmVkIHJlZ3Jlc3Npb24gdHJlZSB3aXRoIGJlc3QgYW5kIG1pbiBjcCB0byBtYWtlIHByZWRpY3Rpb25zIGFuZCBzaW5jZSB3ZSBkaWQgbm90IHVzZSBhbGwgdmFyaWFibGVzLCB3ZSBmaXQgZml0IHR3byBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMgYW5kIGNvbXBhcmUgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSB0aHJlZSBtb2RlbHMuDQoNClRoZSAyIGxpbmVhciByZWdyZXNzaW9uIG1vZGVscyB3ZXJlIGJ1aWx0IGFzIGZvbGxvd3M6IA0KDQogKyBMU0UwMTogaW5jbHVkZXMgImJvZHkuZmF0Xy4iICsgImdlbmRlciIgKyAiYnJvYWQuanVtcF9jbSIgKyAiY2xhc3MiICsgImFnZSIuDQogKyBMU0UwMjogaW5jbHVkaW5nIGFsbCB2YXJpYWJsZXMgdGhyb3VnaCBzdGVwLXdpc2UgdmFyaWFibGUgc2VsZWN0aW9uLg0KDQpgYGB7cn0NCg0KIyA1LiBQcmVkaWN0aW9uDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGVzdCBkYXRhDQpwcmVkLmJlc3QuY3AgPC0gcHJlZGljdChwcnVuZWQudHJlZS5iZXN0LmNwLCBuZXdkYXRhID0gdGVzdC5kYXRhKQ0KcHJlZC5taW4uY3AgPC0gcHJlZGljdChwcnVuZWQudHJlZS5taW4uY3AsIG5ld2RhdGEgPSB0ZXN0LmRhdGEpDQoNCg0KIyBFdmFsdWF0ZSBtb2RlbCBwZXJmb3JtYW5jZTogYmVzdC5jcA0KbXNlLnRyZWUuYmVzdC5jcCA8LSBtZWFuKCh0ZXN0LmRhdGEkc2l0LnVwcy5jb3VudHMgLSBwcmVkLmJlc3QuY3ApXjIpDQpybXNlLnRyZWUuYmVzdC5jcCA8LSBzcXJ0KG1zZS50cmVlLmJlc3QuY3ApDQpyLnNxdWFyZWQudHJlZS5iZXN0LmNwIDwtIGNvcih0ZXN0LmRhdGEkc2l0LnVwcy5jb3VudHMsIHByZWQuYmVzdC5jcCleMg0KIyBtaW4uY3ANCm1zZS50cmVlLm1pbi5jcCA8LSBtZWFuKCh0ZXN0LmRhdGEkc2l0LnVwcy5jb3VudHMgLSBwcmVkLm1pbi5jcCleMikNCnJtc2UudHJlZS5taW4uY3AgPC0gc3FydChtc2UudHJlZS5taW4uY3ApDQpyLnNxdWFyZWQudHJlZS5taW4uY3AgPC0gY29yKHRlc3QuZGF0YSRzaXQudXBzLmNvdW50cywgcHJlZC5taW4uY3ApXjINCg0KIyMNCiMgZml0IG9yZGluYXJ5IGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIA0KTFNFMDEgPC0gbG0oc2l0LnVwcy5jb3VudHMgfiBib2R5LmZhdF8uICsgZ2VuZGVyICsgYnJvYWQuanVtcF9jbSArIGNsYXNzICsgYWdlLCBkYXRhID0gdHJhaW4uZGF0YSkNCnByZWQubHNlMDEgPC0gIHByZWRpY3QoTFNFMDEsIG5ld2RhdGEgPSB0ZXN0LmRhdGEpDQptc2UubHNlMDEgPC0gbWVhbigodGVzdC5kYXRhJHNpdC51cHMuY291bnRzIC0gcHJlZC5sc2UwMSleMikNCnJtc2UubHNlMDEgPC0gc3FydChtc2UubHNlMDEpDQpyLnNxdWFyZWQubHNlMDEgPC0gY29yKHRlc3QuZGF0YSRzaXQudXBzLmNvdW50cywgcHJlZC5sc2UwMSleMg0KDQojIw0KIyMgb3JkaW5hcnkgTFNFIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBzdGVwLXdpc2UgdmFyaWFibGUgc2VsZWN0aW9uDQpsc2UwMi5maXQgPC0gbG0oc2l0LnVwcy5jb3VudHN+LixkYXRhID0gdHJhaW4uZGF0YSkNCkFJQy5maXQgPC0gc3RlcEFJQyhsc2UwMi5maXQsIGRpcmVjdGlvbj0iYm90aCIsIHRyYWNlID0gRkFMU0UpDQpwcmVkLmxzZTAyIDwtIHByZWRpY3QoQUlDLmZpdCwgdGVzdC5kYXRhKQ0KbXNlLmxzZTAyIDwtIG1lYW4oKHRlc3QuZGF0YSRzaXQudXBzLmNvdW50cyAtIHByZWQubHNlMDIpXjIpICAgICMgbWVhbiBzcXVhcmUgZXJyb3INCnJtc2UubHNlMDIgPC0gc3FydChtc2UubHNlMDIpICAgICAgICAgICAgICAgICAgICAgICAjIHJvb3QgbWVhbiBzcXVhcmUgZXJyb3INCnIuc3F1YXJlZC5sc2UwMiA8LSAoY29yKHRlc3QuZGF0YSRzaXQudXBzLmNvdW50cywgcHJlZC5sc2UwMikpXjIgIyByLXNxdWFyZWQNCg0KIyMjDQpFcnJvcnMgPC0gY2JpbmQoTVNFID0gYyhtc2UudHJlZS5iZXN0LmNwLCBtc2UudHJlZS5taW4uY3AsIG1zZS5sc2UwMSwgbXNlLmxzZTAyKSwNCiAgICAgICAgICAgICAgICBSTVNFID0gYyhybXNlLnRyZWUuYmVzdC5jcCwgcm1zZS50cmVlLm1pbi5jcCwgcm1zZS5sc2UwMSwgcm1zZS5sc2UwMiksDQogICAgICAgICAgICAgICAgci5zcXVhcmVkID0gYyhyLnNxdWFyZWQudHJlZS5iZXN0LmNwLCByLnNxdWFyZWQudHJlZS5taW4uY3AsIHIuc3F1YXJlZC5sc2UwMSwgci5zcXVhcmVkLmxzZTAyKSkNCnJvd25hbWVzKEVycm9ycykgPSBjKCJ0cmVlLmJlc3QuY3AiLCAidHJlZS5taW4uY3AiLCAibHNlMDEiLCAibHNlMDIiKQ0KcGFuZGVyKEVycm9ycykNCg0KYGBgDQoNCnVubGlrZSB0aGUgZXhwZWN0YXRpb24sIGl0IHNlZW1zIHRoYXQgdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIG91dCBwZXJmb3JtIHRoZSB0cmVlIHdoZXJlIHdlIGNvbXBhcmUgImxzZTIiIHdpdGggIm1pbiBjcCIuDQoNCnwqKk1vZGVsKiogICAgICB8KipNU0Ug4oaTKioJICB8KipSwrIqKiDihpEJICB8KipJbnRlcnByZXRhdGlvbioqICAgICAgICAgICAgICAgICAgICAgfA0KfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnxsc2UwMgkgICAgICAgIHw1My4xNCAgICAgICAgfDAuNzQ0ICAgIAl8TG93ZXIgZXJyb3IgKyBjYXB0dXJlcyB+NzQlIG9mIHZhcmlhbmNlfA0KfHRyZWUuYmVzdC5jcAkgIHw2MS44NSAgICAgICAgfDAuNzAyICAgIAl8U2ltcGxlciwgYnV0IG5vdCBhcyBhY2N1cmF0ZSAgICAgICAgICAgfA0KDQpIZW5jZSwgZXZlbiB3aXRoIGNwIHR1bmluZywgdHJlZXMgaGF2ZSBhIGNlaWxpbmcgaW4gaG93IHdlbGwgdGhleSBjYW4gbW9kZWwgc21vb3RoLCBjb250aW51b3VzIGRhdGEuIFJlZ3Jlc3Npb24gbW9kZWxzLCBwYXJ0aWN1bGFybHkgd2hlbiB3ZWxsLXNwZWNpZmllZCwgb2Z0ZW4ganVzdCBkbyBiZXR0ZXIgZm9yIHRoYXQga2luZCBvZiBwcm9ibGVtLg0KDQpIb3dldmVyLCBhcyB3ZSBjb21wYXJlIGJvdGggb3V0Y29tZSwgd2UgY2FuIHdlbGwgdXRpbGl6ZSB0aGUgdHJlZSBpZiBuZWVkZWQsIGFzIGl0IGNvbnRpbnVlIHRvIHByb3ZpZGUgc3Ryb25nIHNlcGFyYXRpb24gd2l0aCBtdWNoIGxlc3MgY29tcGxleGl0eS4NCg0KDQojIyBWYXJpYWJsZSBpbXBvcnRhbmNlIEJlc3QgQ1ANCg0KVmFyaWFibGUgaW1wb3J0YW5jZSBpbiByZWdyZXNzaW9uIHRyZWVzIGlkZW50aWZpZXMgd2hpY2ggcHJlZGljdG9ycyBoYXZlIHRoZSBzdHJvbmdlc3QgaW5mbHVlbmNlIG9uIHRoZSB0YXJnZXQgdmFyaWFibGXigJlzIHByZWRpY3Rpb25zDQoNCkNvbXBhcmluZyB2YXJpYWJsZXMgaW1wb3J0YW5jZSBiZXR3ZWVuIG1pbmltdW0gY3AgYW5kIGJlc3QgY3Agc2hvcyB0aGF0IGJvdGggaGF2ZSB0aGUgc2FtZSBkdHJpYnV0aW9ucyBhcyBvdXRsaW5lZCBpbiB0aGUgYmVsb3cgMiBncmFwaHMuIA0KDQpgYGB7cn0NCmltcG9ydGFuY2UgPC0gcHJ1bmVkLnRyZWUuYmVzdC5jcCR2YXJpYWJsZS5pbXBvcnRhbmNlDQpiYXJwbG90KHNvcnQoaW1wb3J0YW5jZSwgZGVjcmVhc2luZyA9IFRSVUUpLCANCiAgICAgICAgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlOiBCZXN0IENQIiwNCiAgICAgICAgY29sID0gInNreWJsdWUiLA0KICAgICAgICBsYXMgPSAyKQ0KYGBgDQoNCg0KIyMgVmFyaWFibGUgaW1wb3J0YW5jZSBNaW5pbXVtIENQDQoNCmBgYHtyfQ0KDQppbXBvcnRhbmNlIDwtIHBydW5lZC50cmVlLm1pbi5jcCR2YXJpYWJsZS5pbXBvcnRhbmNlDQpiYXJwbG90KHNvcnQoaW1wb3J0YW5jZSwgZGVjcmVhc2luZyA9IFRSVUUpLCANCiAgICAgICAgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlOiBNaW5pbXVtIENQIiwNCiAgICAgICAgY29sID0gInNreWJsdWUiLA0KICAgICAgICAgICAgICAgIGxhcyA9IDIpDQoNCmBgYA0KDQpsb29raW5nIGF0IHRoZSBhYm92ZSB0cmVlIHdlIGNhbiBzZWUgdGhhdCBzb21lIHZhcmlhYmxlcyBoYXZlIGhpZ2hlciBpbXBvcnRhbmNlIGJ1dCBub3Qgc2hvd2luZyBpbiB0aGUgdHJlZS4gSXQgaXMgbm90IHVuY29tbW9uIHRoYXQgc29tZSB2YXJpYWJsZXMgaW4gdGhlIHZhcmlhYmxlIGltcG9ydGFuY2UgbGlzdCBidXQgbm90IHNob3duIGluIHRoZSBmaW5hbCByZWdyZXNzaW9uIGFuZCBjbGFzc2lmaWNhdGlvbiB0cmVlcy4gDQoNCkltcG9ydGFuY2UgaXMgbWVhc3VyZWQgYnkgcGVyZm9ybWFuY2UgZ2FpbiAoZS5nLiwgcmVkdWN0aW9uIGluIE1TRS9HaW5pIGltcHVyaXR5KS5UaGlzIGNhcHR1cmVzIGhvdyBtdWNoIGEgdmFyaWFibGUgaW1wcm92ZXMgbW9kZWwgYWNjdXJhY3kgYnV0IGRvZXMgbm90IGltcGx5IHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZS4gDQoNCkxhcmdlciBjb2VmZmljaWVudHMgaW5kaWNhdGUgc3Ryb25nZXIgYXNzb2NpYXRpb25zIHdpdGggdGhlIG91dGNvbWUsIHRob3VnaCBjb3JyZWxhdGlvbiBkb2VzIG5vdCBpbXBseSBjYXVzYXRpb24gYW5kIGFzIHN1Y2ggYXMgaGVpZ2h0IGFuZCBncmlwIGZvcmNlIG1vcmUgbGlua2VkIHRvIHZhcmlhYmxlIGxpa2UgZ2VuZGVyIHdoZXJlIG1hbGVzIGhhdmUgdGFsbGVyIGFuZCBzdHJvbmdlciBncmlwIGZvcmNlIGJ1dCB0aGlzIGNvbmZvdW5kIHdpdGggZ2VuZGVyIGFuZCBzaW5jZSB0cmVlIHVzZWQgZ2VuZGVyIHRoZW4gdGhlc2UgYXJlIGJlY29taW5nIHJlZHVuZGVudCB2YXJpYWJsZXMgaW4gdHJlZSBidWlsZC4NCg0KRm9yIGNvbXBhcmlzb24sIHdlIGFsc28gcHJpbnQgb3V0IHRoZSBpbmZlcmVudGlhbCB0YWJsZSBvZiB0aGUgc3RlcC13aXNlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGluIHRoZSBmb2xsb3dpbmcuDQoNCmBgYHtyfQ0KDQpwYW5kZXIoc3VtbWFyeShBSUMuZml0KSRjb2VmKQ0KDQpgYGANCg0KIyMgQ29uY2x1c2lvbg0KDQpXZSB3b3VsZCByZWNvbW1lbmQgdXNpbmcgdGhlIHRyZWUgdGhvdWdoIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgb3V0cGVyZm9ybSB0aGUgdHJlZSBzaW5jZSB3ZSBkaWRuJ3Qgbm90IHJlbW92ZSB2YXJpYWJsZXMgdGhhdCBoYXZlIHN0cm9uZyBjb3JyZWxhdGlvbiBidXQgbm90IGNhdXNhdGlvbiByZWxhdGlvbnNoaXAgd2l0aCB0aGUgdHJlYXRlZCB2YXJpYWJsZSBzdWNoIGFzICJicm9hZC5qdW1wX2NtIg0KDQojIEVzdGltYXRpbmcgYW5kIFByZWRpY3RpbmcgUGVyZm9ybWFuY2UgQ2xhc3MNCg0KVG8gcHJlZGljdCBhbmQgY2FsY3VsYXRlIGNsYXNzIHZhcmlhYmxlLCB3ZSB1c2UgQ2xhc3NpZmljYXRpb24gdHJlZXMuIENsYXNzaWZpY2F0aW9uIHRyZWVzIGFyZSBhIHR5cGUgb2Ygc3VwZXJ2aXNlZCBsZWFybmluZyBhbGdvcml0aG0gdGhhdCByZWN1cnNpdmVseSBwYXJ0aXRpb25zIHRoZSBmZWF0dXJlIHNwYWNlIHRvIHByZWRpY3QgY2F0ZWdvcmljYWwgdGFyZ2V0IHZhcmlhYmxlcy4NCg0KIyMgR3JvdyBJbml0aWFsIFRyZWUNClRoZSBpbml0aWFsIHRyZWUgc2l6ZSBpcyBjb250cm9sbGVkIGJ5IHNvbWUgZGVmYXVsdCBoeXBlci1wYXJhbWV0ZXJzICJycGFydC5jb250cm9sKCkiLiBJdCB0ZW5kcyB0byBiZSBvdmVyLWZpdHRlZC4NCg0KV2hpbGUgaXQgaXMgY29tbW9uIGluIGNsYXNzaWZpY2F0aW9uIHRoYXQgdGhlIGtleSBjaGFsbGVuZ2UgaXMgdGhhdCB0aGUgY2xhc3NlcyAoY2F0ZWdvcmllcykgYXJlIG5vdCBlcXVhbGx5IHJlcHJlc2VudGVkLmhvd2V2ZXIsIGluIG91ciBkYXRhc2V0IHRoZSBjbGFzc2VzIGFyZSBlcXVhbGx5IGRpc3RyaWJ1dGVkIGFjcm9zcyB0aGUgNCBjbGFzc2VzLiBob3dldnIuIHNpbmNlIHdlIGhhdmUgNCBsZXZlbHMgaW4gImNsYXNzIiB2YXJpYWJsZSwgd2UgY29tYmluZSB0aGVzZSB0byBwcmlkaWN0IHRoZSBwb3JiYWJpbGl0eSBvZiBhdGhlbGV0IGJlaW5nIGNsYXNzaWZpZWQgYXMgIkEiIG9yICJOb3QgQSIuDQoNCkEgd2UgYXJlIGJ1aWxkaW5nIHRoZSB0cmVlLCB3ZSBzdGFydCB3aXRoIHRoZSBlbnRpcmUgZGF0YXNldCBhdCB0aGUgcm9vdCBub2RlIGFuZCB0aGVuIHJlY3Vyc2l2ZWx5IHNwbGl0IHRoZSBkYXRhIGludG8gcHVyZXIgc3Vic2V0cw0KDQpPcHRpbWFsIFRyZWUgU2l6ZTogVHlwaWNhbGx5IHdoZXJlIHhlcnJvciBpcyBtaW5pbWl6ZWQNCg0KYGBge3J9DQoNCiMgU3RlcCAxOiBSZWNvZGUgdGhlIHRhcmdldCB2YXJpYWJsZSBpbnRvIGJpbmFyeQ0KZGF0YSRiaW5hcnlfY2xhc3MgPC0gaWZlbHNlKGRhdGEkY2xhc3MgPT0gIkEiLCAxLCAwKQ0KZGF0YSRiaW5hcnlfY2xhc3MgPC0gYXMuZmFjdG9yKGRhdGEkYmluYXJ5X2NsYXNzKQ0KDQojIFN0ZXAgMjogU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMNCnNldC5zZWVkKDEyMykNCnRyYWluLmluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0YSRiaW5hcnlfY2xhc3MsIHAgPSAwLjcsIGxpc3QgPSBGQUxTRSkNCnRyYWluLmRhdGEgPC0gZGF0YVt0cmFpbi5pbmRleCwgXQ0KdGVzdC5kYXRhIDwtIGRhdGFbLXRyYWluLmluZGV4LCBdDQoNCiMgU3RlcCAzOiBGaXQgdGhlIGNsYXNzaWZpY2F0aW9uIHRyZWUgdXNpbmcgdGhlIG5ldyBiaW5hcnkgdGFyZ2V0DQoNCnRyZWUubW9kZWwgPC0gcnBhcnQoYmluYXJ5X2NsYXNzIH4gLiwgDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhWywgIShuYW1lcyh0cmFpbi5kYXRhKSAlaW4lICJjbGFzcyIpXSwgICMgZHJvcCBvcmlnaW5hbCBjbGFzcw0KICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLCAgICMgY2xhc3NpZmljYXRpb24gdHJlZQ0KICAgICAgICAgICAgICAgICAgICBwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiZ2luaSIsICAjIFVzaW5nIEdpbmkgaW5kZXgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRk4gY29zdCA9IDEsIEZQIGNvc3QgPSAwLjUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvc3MgPSBtYXRyaXgoYygwLCAwLjUsIDEsIDApLCBucm93ID0gMikgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAyMCwgICMgTWluIDE1IG9icyB0byBzcGxpdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmJ1Y2tldCA9IDEwLCAgICMgTWluIDcgb2JzIGluIGxlYWYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENvbXBsZXhpdHkgcGFyYW1ldGVyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3AgPSAwLjAwMSwgIyBjb21wbGV4IHBhcmFtZXRlcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGRlcHRoID0gNykpICAgIyBNYXggdHJlZSBkZXB0aA0KDQoNCmBgYA0KDQoNCg0KYGBge3J9DQpycGFydC5wbG90KHRyZWUubW9kZWwsIA0KICAgICAgICAgICBleHRyYSA9IDEwNCwgIyBjaGVjayB0aGUgaGVscCBkb2N1bWVudCBmb3IgbW9yZSBpbmZvcm1hdGlvbg0KICAgICAgICAgICAjIGNvbG9yIHBhbGV0dGUgaXMgYSBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZSB0aGF0IGJsZW5kcyBncmVlbiAoRykgdG8gYmx1ZSAoQnUpDQogICAgICAgICAgIGJveC5wYWxldHRlID0gIkduQnUiLCAgDQogICAgICAgICAgIGJyYW5jaC5sdHkgPSAxLCANCiAgICAgICAgICAgc2hhZG93LmNvbCA9ICJncmF5IiwgDQogICAgICAgICAgIG5uID0gVFJVRSkNCmBgYA0KDQojIyBQcnVuaW5nIFRyZWUNCg0KYGBge3J9DQojUHJpbnQgdGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyIHRhYmxlDQpwYW5kZXIodHJlZS5tb2RlbCRjcHRhYmxlKQ0KDQpgYGANCg0KVGhlIGJlbG93IGdyYXBoIGdpdmVzIHRoZSByZWZlcmVuY2UgbGluZSAoYnJva2VuIGxpbmUpIGZvciAxLVNFIHJ1bGUuIFRoZSBudW1iZXJzIG9uIHRoZSB0b3Agb2YgdGhlIHBsb3QgcmVwcmVzZW50IHRoZSBsZWFmIG5vZGVzIGluIHRoZSBmaW5hbCB0cmVlIGRpYWdyYW0uDQoNCmBgYHtyfQ0KIyBQcmludCBjcCB0YWJsZQ0KI3ByaW50Y3AodHJlZS5tb2RlbCkNCg0KIyBQbG90IGNwIHZzIGNyb3NzLXZhbGlkYXRlZCBlcnJvcg0KcGxvdGNwKHRyZWUubW9kZWwpDQoNCmBgYA0KDQpGb3IgY2xhcml0eSBpbiB0aGUgYW5hbHlzaXMsIHdlIGludHJvZHVjZSB0d28gbm90YXRpb25zOiBtaW4uY3AgcmVwcmVzZW50cyB0aGUgY3AgdmFsdWUgeWllbGRpbmcgdGhlIG1pbmltdW0gY3Jvc3MtdmFsaWRhdGlvbiBlcnJvciwgd2hpbGUgMVNFLmNwIGRlbm90ZXMgdGhlIGNwIHZhbHVlIHNlbGVjdGVkIGJ5IHRoZSBtb3JlIGNvbnNlcnZhdGl2ZSAxLVNFIHJ1bGUgKG1pbmltYWwgZXJyb3IgcGx1cyBvbmUgc3RhbmRhcmQgZXJyb3IpLg0KDQpUaGUgYmVsb3ctcHJ1bmVkIHRyZWUgZGlhZ3JhbSBpcyBiYXNlZCBvbiB0aGUgMS1TRSBydWxlLiBOZXh0LCB3ZSBwbG90IHRoZSB0cmVlIGRpYWdyYW0gYmFzZWQgb24gdGhlIG1pbmltdW0gY3Jvc3MtdmFsaWRhdGlvbiBlcnJvci4NCg0KDQpgYGB7cn0NCg0KIyBGaW5kIHRoZSBvcHRpbWFsIGNwIHZhbHVlIHRoYXQgbWluaW1pemVzIGNyb3NzLXZhbGlkYXRlZCBlcnJvcg0KbWluLmNwIDwtIHRyZWUubW9kZWwkY3B0YWJsZVt3aGljaC5taW4odHJlZS5tb2RlbCRjcHRhYmxlWywieGVycm9yIl0pLCJDUCJdDQoNCiMgUHJ1bmUgdGhlIHRyZWUgdXNpbmcgdGhlIG9wdGltYWwgY3ANCnBydW5lZC50cmVlLjFTRSA8LSBwcnVuZSh0cmVlLm1vZGVsLCBjcCA9IDAuMDAxKSAgDQpwcnVuZWQudHJlZS5taW4gPC0gcHJ1bmUodHJlZS5tb2RlbCwgY3AgPSBtaW4uY3ApDQoNCiMgVmlzdWFsaXplIHRoZSBwcnVuZWQgdHJlZQ0KcnBhcnQucGxvdChwcnVuZWQudHJlZS4xU0UsIA0KICAgICAgICAgICBleHRyYSA9IDEwNCwgIyBjaGVjayB0aGUgaGVscCBkb2N1bWVudCBmb3IgbW9yZSBpbmZvcm1hdGlvbg0KICAgICAgICAgICAjIGNvbG9yIHBhbGV0dGUgaXMgYSBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZSB0aGF0IGJsZW5kcyBncmVlbiAoRykgdG8gYmx1ZSAoQnUpDQogICAgICAgICAgIGJveC5wYWxldHRlID0gIkduQnUiLCAgDQogICAgICAgICAgIGJyYW5jaC5sdHkgPSAxLCANCiAgICAgICAgICAgc2hhZG93LmNvbCA9ICJncmF5IiwgDQogICAgICAgICAgIG5uID0gVFJVRSwNCiAgICAgICAgICAgbWFpbiA9ICJQcnVuZWQgQ2xhc3NpZmljYXRpb24gVHJlZSAoMS1TRSBSdWxlKSIpDQoNCmBgYA0KDQpUaGUgYWJvdmUtcHJ1bmVkIHRyZWUgZGlhZ3JhbSBpcyBiYXNlZCBvbiB0aGUgMS1TRSBydWxlLiBOZXh0LCB3ZSBwbG90IHRoZSB0cmVlIGRpYWdyYW0gYmFzZWQgb24gdGhlIG1pbmltdW0gY3Jvc3MtdmFsaWRhdGlvbiBlcnJvcg0KDQpgYGB7cn0NCg0KIyBWaXN1YWxpemUgdGhlIHBydW5lZCB0cmVlDQpycGFydC5wbG90KHBydW5lZC50cmVlLm1pbiwgDQogICAgICAgICAgIGV4dHJhID0gMTA0LCAjIGNoZWNrIHRoZSBoZWxwIGRvY3VtZW50IGZvciBtb3JlIGluZm9ybWF0aW9uDQogICAgICAgICAgICMgY29sb3IgcGFsZXR0ZSBpcyBhIHNlcXVlbnRpYWwgY29sb3Igc2NoZW1lIHRoYXQgYmxlbmRzIGdyZWVuIChHKSB0byBibHVlIChCdSkNCiAgICAgICAgICAgYm94LnBhbGV0dGUgPSAiR25CdSIsICANCiAgICAgICAgICAgYnJhbmNoLmx0eSA9IDEsIA0KICAgICAgICAgICBzaGFkb3cuY29sID0gImdyYXkiLCANCiAgICAgICAgICAgbm4gPSBUUlVFLA0KICAgICAgICAgICBtYWluID0gIlBydW5lZCBDbGFzc2lmaWNhdGlvbiBUcmVlIChNaW4gQ3Jvc3MgVmFsaWRhdGlvbikiKQ0KDQpgYGANCg0KIyMgR2xvYmFsIFBlcmZvcm1hbmNlIHdpdGggUk9DDQoNCkNsYXNzaWZpY2F0aW9uIHRyZWVzIG1ha2UgcHJlZGljdGlvbnMgYnkgcm91dGluZyBvYnNlcnZhdGlvbnMgdGhyb3VnaCBhIHNlcmllcyBvZiBoaWVyYXJjaGljYWwgc3BsaXRzLCBzdGFydGluZyBhdCB0aGUgcm9vdCBub2RlIGFuZCBlbmRpbmcgYXQgYSB0ZXJtaW5hbCBsZWFmIG5vZGUuIEVhY2ggc3BsaXQgYXBwbGllcyBhIGRlY2lzaW9uIHJ1bGUgYmFzZWQgb24gZmVhdHVyZSB2YWx1ZXMuIA0KDQpgYGB7cn0NCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldA0KcHJlZC5sYWJlbC4xU0UgPC0gcHJlZGljdChwcnVuZWQudHJlZS4xU0UsIHRlc3QuZGF0YSwgdHlwZSA9ICJjbGFzcyIpICMgZGVmYXVsdCBjdXRvZmYgMC41DQpwcmVkLnByb2IuMVNFIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUuMVNFLCB0ZXN0LmRhdGEsIHR5cGUgPSAicHJvYiIpWywyXQ0KIyMNCnByZWQubGFiZWwubWluIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUubWluLCB0ZXN0LmRhdGEsIHR5cGUgPSAiY2xhc3MiKSAjIGRlZmF1bHQgY3V0b2ZmIDAuNQ0KcHJlZC5wcm9iLm1pbiA8LSBwcmVkaWN0KHBydW5lZC50cmVlLm1pbiwgdGVzdC5kYXRhLCB0eXBlID0gInByb2IiKVssMl0NCg0KIyBDb25mdXNpb24gbWF0cml4DQojY29uZi5tYXRyaXggPC0gY29uZnVzaW9uTWF0cml4KHByZWQubGFiZWwsIHRlc3QuZGF0YSRkaWFiZXRlcywgcG9zaXRpdmUgPSAicG9zIikNCiNwcmludChjb25mLm1hdHJpeCkNCg0KdHJhaW4uZGF0YSRiaW5hcnlfY2xhc3MgPC0gZGF0YSRiaW5hcnlfY2xhc3NbdHJhaW4uaW5kZXhdDQp0ZXN0LmRhdGEkYmluYXJ5X2NsYXNzIDwtIGRhdGEkYmluYXJ5X2NsYXNzWy10cmFpbi5pbmRleF0NCg0KIyBSZW1vdmUgY2xhc3MgY29sdW1uIGlmIGl0IHN0aWxsIGV4aXN0cw0KdHJhaW4uZGF0YSRjbGFzcyA8LSBOVUxMDQp0ZXN0LmRhdGEkY2xhc3MgPC0gTlVMTA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMjIyAgbG9naXN0aWMgcmVncmVzc2lvbg0KbG9naXQuZml0IDwtIGdsbShiaW5hcnlfY2xhc3MgfiAuLCBkYXRhID0gdHJhaW4uZGF0YSwgZmFtaWx5ID0gYmlub21pYWwpDQpBSUMubG9naXQgPC0gc3RlcChsb2dpdC5maXQsIGRpcmVjdGlvbiA9ICJib3RoIiwgdHJhY2UgPSAwKQ0KcHJlZC5sb2dpdCA8LSBwcmVkaWN0KEFJQy5sb2dpdCwgdGVzdC5kYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCg0KIyBST0MgY3VydmUgYW5kIEFVQw0Kcm9jLnRyZWUuMVNFIDwtIHJvYyh0ZXN0LmRhdGEkYmluYXJ5X2NsYXNzLCBwcmVkLnByb2IuMVNFKQ0Kcm9jLnRyZWUubWluIDwtIHJvYyh0ZXN0LmRhdGEkYmluYXJ5X2NsYXNzLCBwcmVkLnByb2IubWluKQ0Kcm9jLmxvZ2l0IDwtIHJvYyh0ZXN0LmRhdGEkYmluYXJ5X2NsYXNzLCBwcmVkLmxvZ2l0KQ0KDQojIw0KIyMjIFNlbi1TcGUNCnRyZWUuMVNFLnNlbiA8LSByb2MudHJlZS4xU0Ukc2Vuc2l0aXZpdGllcw0KdHJlZS4xU0Uuc3BlIDwtIHJvYy50cmVlLjFTRSRzcGVjaWZpY2l0aWVzDQojDQp0cmVlLm1pbi5zZW4gPC0gcm9jLnRyZWUubWluJHNlbnNpdGl2aXRpZXMNCnRyZWUubWluLnNwZSA8LSByb2MudHJlZS5taW4kc3BlY2lmaWNpdGllcw0KDQojDQpsb2dpdC5zZW4gPC0gcm9jLmxvZ2l0JHNlbnNpdGl2aXRpZXMNCmxvZ2l0LnNwZSA8LSByb2MubG9naXQkc3BlY2lmaWNpdGllcw0KIyMgQVVDDQphdWMudHJlZS4xU0UgPC0gcm9jLnRyZWUuMVNFJGF1Yw0KYXVjLnRyZWUubWluIDwtIHJvYy50cmVlLm1pbiRhdWMNCmF1Yy5sb2dpdCA8LSByb2MubG9naXQkYXVjDQoNCiMjIw0KcGxvdCgxLWxvZ2l0LnNwZSwgbG9naXQuc2VuLCAgDQogICAgIHhsYWIgPSAiMSAtIHNwZWNpZmljaXR5IiwNCiAgICAgeWxhYiA9ICJzZW5zaXRpdml0eSIsDQogICAgIGNvbCA9ICJkYXJrcmVkIiwNCiAgICAgdHlwZSA9ICJsIiwNCiAgICAgbHR5ID0gMSwNCiAgICAgbHdkID0gMSwNCiAgICAgbWFpbiA9ICJST0M6IENBUlQgYW5kIExvZ2lzdGljIFJlZ3Jlc3NvcG0iKQ0KbGluZXMoMS10cmVlLjFTRS5zcGUsIHRyZWUuMVNFLnNlbiwgDQogICAgICBjb2wgPSAiYmx1ZSIsDQogICAgICBsdHkgPSAxLA0KICAgICAgbHdkID0gMSkNCmxpbmVzKDEtdHJlZS5taW4uc3BlLCB0cmVlLm1pbi5zZW4sICAgICAgDQogICAgICBjb2wgPSAib3JhbmdlIiwNCiAgICAgIGx0eSA9IDEsDQogICAgICBsd2QgPSAxKQ0KYWJsaW5lKDAsMSwgY29sID0gInNreWJsdWUzIiwgbHR5ID0gMiwgbHdkID0gMikNCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBjKCJMb2dpc3RpYyIsICJUcmVlIDFTRSIsICJUcmVlIE1pbiIpLA0KICAgICAgIGx0eSA9IGMoMSwxLDEpLCBsd2QgPSByZXAoMSwzKSwNCiAgICAgICBjb2wgPSBjKCJyZWQiLCAiYmx1ZSIsICJvcmFuZ2UiKSwNCiAgICAgICBidHk9Im4iLGNleCA9IDAuOCkNCiMjIGFubm90YXRpb24gLSBBVUMNCnRleHQoMC44LCAwLjQ2LCBwYXN0ZSgiTG9naXN0aWMgQVVDOiAiLCByb3VuZChhdWMubG9naXQsNCkpLCBjZXggPSAwLjgpDQp0ZXh0KDAuOCwgMC40LCBwYXN0ZSgiVHJlZSAxU0UgQVVDOiAiLCByb3VuZChhdWMudHJlZS4xU0UsNCkpLCBjZXggPSAwLjgpDQp0ZXh0KDAuOCwgMC4zNCwgcGFzdGUoIlRyZWUgTWluIEFVQzogIiwgcm91bmQoYXVjLnRyZWUubWluLDQpKSwgY2V4ID0gMC44KQ0KDQpgYGANCg0KVGhlIFJPQyBjdXJ2ZXMgYW5kIGNvcnJlc3BvbmRpbmcgQVVDIHZhbHVlcyBkZW1vbnN0cmF0ZSB0aGF0IHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGFjaGlldmVzIG1hcmdpbmFsbHkgYmV0dGVyIHBlcmZvcm1hbmNlIGNvbXBhcmVkIHRvIGJvdGggcHJ1bmVkIHRyZWUgbW9kZWxzLCB3aXRoIHRoZSBtb3JlIGNvbXBsZXggdHJlZSAocHJ1bmVkIHVzaW5nIG1pbmltdW0gY3Jvc3MtdmFsaWRhdGlvbiBlcnJvcikgc2FtZSBwcmVkaWN0aXZlIGFiaWxpdHkgdGhhbiB0aGUgc2ltcGxlciB0cmVlIHBydW5lZCBhY2NvcmRpbmcgdG8gdGhlIDEtU0UgcnVsZS4NCg0KIyMgT3B0aW1hbCBDdXQtb2ZmIFByb2JhYmlsaXR5DQoNCkluIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiwgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgbXVzdCBiZSBjb252ZXJ0ZWQgaW50byBjbGFzcyBsYWJlbHMgKGUuZy4sIDAgb3IgMSkgYnkgYXBwbHlpbmcgYSBjdXQtb2ZmIHRocmVzaG9sZC4gVGhlIGNob2ljZSBvZiB0aGlzIHRocmVzaG9sZCBzaWduaWZpY2FudGx5IGltcGFjdHMgbW9kZWwgcGVyZm9ybWFuY2UsIGFzIGl0IGJhbGFuY2VzIGFjY3VyYWN5LCBzZW5zaXRpdml0eSAocmVjYWxsKSwgYW5kIHNwZWNpZmljaXR5Lg0KDQpLZXkgYXBwcm9hY2hlcyB0byBkZXRlcm1pbmUgdGhlIG9wdGltYWwgY3V0LW9mZjoNCg0KSS4gVHJhZGUtb2ZmIEJldHdlZW4gU2Vuc2l0aXZpdHkgYW5kIFNwZWNpZmljaXR5DQpJSS4gQWNjdXJhY3ktRHJpdmVuIEN1dC1vZmYNCklJSS4gQ29zdC1TZW5zaXRpdmUgVGhyZXNob2xkDQpJVi4gUk9DIGFuZCBQcmVjaXNpb24tUmVjYWxsIEN1cnZlcw0KVi4gUHJhY3RpY2FsIENvbnNpZGVyYXRpb25zDQoNCiMjIyBDdXQtb2ZmIFZlcnN1cyBNaXNjbGFzc2lmaWNhdGlvbiBDb3N0DQoNCmBgYHtyfQ0KIyBQcmVkaWN0aXZlIHByb2JhYmlsaXRpZXMgb2YgdGhlIHBydW5lZCB0cmVlDQpwcmVkLnByb2IubWluIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUubWluLCB0cmFpbi5kYXRhLCB0eXBlID0gInByb2IiKVssIDJdDQoNCiMgQ3V0b2ZmIHZhbHVlcw0KY3V0b2ZmIDwtIHNlcSgwLCAxLCBsZW5ndGggPSAxMCkNCmNvc3QgPC0gbnVtZXJpYyhsZW5ndGgoY3V0b2ZmKSkNCg0KIyBNaXNjbGFzc2lmaWNhdGlvbiBjb3N0IGZvciBlYWNoIGN1dG9mZg0KZm9yIChpIGluIHNlcV9hbG9uZyhjdXRvZmYpKSB7DQogIHByZWQubGFiZWwgPC0gaWZlbHNlKHByZWQucHJvYi5taW4gPiBjdXRvZmZbaV0sIDEsIDApDQogIEZOIDwtIHN1bShwcmVkLmxhYmVsID09IDAgJiB0cmFpbi5kYXRhJGJpbmFyeV9jbGFzcyA9PSAxKQ0KICBGUCA8LSBzdW0ocHJlZC5sYWJlbCA9PSAxICYgdHJhaW4uZGF0YSRiaW5hcnlfY2xhc3MgPT0gMCkNCiAgY29zdFtpXSA8LSA1ICogRlAgKyAyMCAqIEZODQp9DQoNCiMgT3B0aW1hbCBjdXRvZmYNCm1pbi5JRCA8LSB3aGljaChjb3N0ID09IG1pbihjb3N0KSkNCm9wdGltLnByb2IgPC0gbWVhbihjdXRvZmZbbWluLklEXSkNCg0KIyBQbG90DQpwbG90KGN1dG9mZiwgY29zdCwgdHlwZSA9ICJiIiwgY29sID0gIm5hdnkiLA0KICAgICBtYWluID0gIkN1dG9mZiB2cyBNaXNjbGFzc2lmaWNhdGlvbiBDb3N0IiwNCiAgICAgeGxhYiA9ICJDdXRvZmYiLCB5bGFiID0gIkNvc3QiKQ0KdGV4dChvcHRpbS5wcm9iLCBtaW4oY29zdCkgKyAyMDAwMCwgDQogICAgIHBhc3RlKCJPcHRpbWFsIGN1dG9mZjoiLCByb3VuZChvcHRpbS5wcm9iLCAzKSksIA0KICAgICBjZXggPSAwLjgsIGNvbCA9ICJkYXJrcmVkIikNCmBgYA0KDQpUaGUgcmVzdWx0aW5nIG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0eSBvZiAwLjIyMiwgZGlzcGxheWVkIG9uIHRoZSBwbG90IGFib3ZlLCB3aWxsIGJlIHVzZWQgdG8gbWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhc2V0LCBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgYWNjdXJhY3kgd2lsbCBiZSByZXBvcnRlZC4gV2UgZW1waGFzaXplIG9uY2UgYWdhaW4gdGhhdCB0aGlzIG9wdGltYWwgdGhyZXNob2xkIGlzIGNob3NlbiBzcGVjaWZpY2FsbHkgdG8gbWluaW1pemUgdGhlIHRvdGFsIGNvc3Qgb2YgbWlzY2xhc3NpZmljYXRpb24uDQoNCg==